软件设计师之设计模式
设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理地运用设计模式可以完美地解决很多问题,每种模式在现实中都有相应的原理来与之对应,每种模式都描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是设计模式能被广泛应用的原因。
什么是 GOF(四人帮,全拼 Gang of Four)?
在 1994 年,由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 四人合著出版了一本名为 Design Patterns - Elements of Reusable Object-Oriented Software(中文译名:设计模式 - 可复用的面向对象软件元素) 的书,该书首次提到了软件开发中设计模式的概念。
四位作者合称 GOF(四人帮,全拼 Gang of Four)。他们所提出的设计模式主要是基于以下的面向对象设计原则。
- 对接口编程而不是对实现编程。
- 优先使用对象组合而不是继承。
设计模式的使用
设计模式在软件开发中的两个主要用途。
开发人员的共同平台
设计模式提供了一个标准的术语系统,且具体到特定的情景。例如,单例设计模式意味着使用单个对象,这样所有熟悉单例设计模式的开发人员都能使用单个对象,并且可以通过这种方式告诉对方,程序使用的是单例模式。
最佳的实践
设计模式已经经历了很长一段时间的发展,它们提供了软件开发过程中面临的一般问题的最佳解决方案。学习这些模式有助于经验不足的开发人员通过一种简单快捷的方式来学习软件设计。
设计模式的类型
根据设计模式的参考书 Design Patterns - Elements of Reusable Object-Oriented Software(中文译名:设计模式 - 可复用的面向对象软件元素) 中所提到的,总共有 23 种设计模式。这些模式可以分为三大类:创建型模式(Creational Patterns)、结构型模式(Structural Patterns)、行为型模式(Behavioral Patterns)。当然,我们还会讨论另一类设计模式:J2EE 设计模式。
序号 | 模式 & 描述 | 包括 |
---|---|---|
1 | 创建型模式 这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。 |
|
2 | 结构型模式 这些模式关注对象之间的组合和关系,旨在解决如何构建灵活且可复用的类和对象结构。 |
|
3 | 行为型模式 这些模式关注对象之间的通信和交互,旨在解决对象之间的责任分配和算法的封装。 |
|
4 | J2EE 模式 这些设计模式特别关注表示层。这些模式是由 Sun Java Center 鉴定的。 |
|
下面用一个图片来整体描述一下设计模式之间的关系:
设计模式的优点
- 提供了一种共享的设计词汇和概念,使开发人员能够更好地沟通和理解彼此的设计意图。
- 提供了经过验证的解决方案,可以提高软件的可维护性、可复用性和灵活性。
- 促进了代码的重用,避免了重复的设计和实现。
- 通过遵循设计模式,可以减少系统中的错误和问题,提高代码质量。
设计模式的六大原则
1、开闭原则(Open Close Principle)
开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
2、里氏代换原则(Liskov Substitution Principle)
里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
3、依赖倒转原则(Dependence Inversion Principle)
这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。
4、接口隔离原则(Interface Segregation Principle)
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。
5、迪米特法则,又称最少知道原则(Demeter Principle)
最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
6、合成复用原则(Composite Reuse Principle)
合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。
设计模式
设计模式基本要素 1、模式名称 2、问题 3、解决方案 4、效果
1、简单工厂模式(Simple Factory)
简单工厂模式是一种创建型设计模式,它通过一个单一的工厂类来负责创建一系列相关的对象。这个工厂类根据传入的参数来决定实例化哪一个具体类的实例。客户端在使用时不需要知道具体的类名,只需要提供必要的信息给工厂。
package com.company.simplefactory;abstract class Product {public abstract void info();
}
package com.company.simplefactory;class ProductA extends Product {@Overridepublic void info() {System.out.println("产品的信息:A");}
}
package com.company.simplefactory;class ProductB extends Product {@Overridepublic void info() {System.out.println("产品的信息:B");}
}
package com.company.simplefactory;class Factory {public static Product createProduct(String type) {Product product = null;switch (type) {case "A":product = new ProductA();break;case "B":product = new ProductB();break;default:System.out.println("没有 " + type + " 类型的产品!");break;}return product;}
}
package com.company.simplefactory;public class SimpleFactory {public static void main(String[] args) {Product productA = Factory.createProduct("A");productA.info();Product productB = Factory.createProduct("B");}
}
适用性
1、当对象的创建过程相对集中,并且客户端对于如何创建对象不感兴趣时,简单工厂可以封装这些创建细节。
2、如果产品的种类相对较少,并且不太可能发生变化,简单工厂可以减少系统的复杂性。
3、当希望避免客户端代码直接实例化对象,而是通过一个统一的接口来创建对象时,简单工厂可以提供这样的接口。
4、简单工厂更适合那些创建逻辑比较简单的情况,如果创建过程涉及复杂的逻辑判断或者条件分支,可能需要考虑其他工厂模式,如工厂方法或抽象工厂。
5、当客户端代码不依赖于产品的具体类,而只需要知道产品的接口或抽象类时,简单工厂可以帮助客户端与具体类解耦。
2、工厂方法模式(Factory Method)
定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使一个类的实例化延迟到其子类。
package com.company.factory;// class Factory
interface Factory {public Product createProduct();
}
package com.company.factory;// abstract class Product
interface Product {// public abstract void info();public void info();
}
package com.company.factory;class FactoryA implements Factory {@Overridepublic Product createProduct() {return new ProductA();}
}
package com.company.factory;class FactoryB implements Factory {@Overridepublic Product createProduct() {return new ProductB();}
}
package com.company.factory;// class ProductA extends Product
class ProductA implements Product {@Overridepublic void info() {System.out.println("产品的信息:A");}
}
package com.company.factory;// class ProductB extends Product
class ProductB implements Product {@Overridepublic void info() {System.out.println("产品的信息:B");}
}
package com.company.factory;public class FactoryMethod {public static void main(String[] args) {Factory factoryA = new FactoryA();// 父类 对象名 = new 子类();Product productA = factoryA.createProduct();// Product productA = new ProductA();productA.info();Factory factoryB = new FactoryB();Product productB = factoryB.createProduct();productB.info();}
}
适用性
Factory Method模式适用于:
1、当一个类不知道它所必须创建的对象的类的时候。
2、当一个类希望由它的子类来指定它所创建的对象的时候。
3、当类创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。
3、抽象工厂模式(Abstract Factory)
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体实现的类。
package com.company.AbstractFactory;interface ProductA {public void info();
}
package com.company.AbstractFactory;class ProductA1 implements ProductA {@Overridepublic void info() {System.out.println("产品的信息:A1");}
}
package com.company.AbstractFactory;class ProductA2 implements ProductA {@Overridepublic void info() {System.out.println("产品的信息:A2");}
}
package com.company.AbstractFactory;interface ProductB {public void info();
}
package com.company.AbstractFactory;class ProductB1 implements ProductB {@Overridepublic void info() {System.out.println("产品的信息:B1");}
}
package com.company.AbstractFactory;class ProductB2 implements ProductB {@Overridepublic void info() {System.out.println("产品的信息:B2");}
}
package com.company.AbstractFactory;interface Factory {public ProductA createProductA();public ProductB createProductB();
}
package com.company.AbstractFactory;class Factory1 implements Factory {@Overridepublic ProductA createProductA() {return new ProductA1();}@Overridepublic ProductB createProductB() {return new ProductB1();}
}
package com.company.AbstractFactory;class Factory2 implements Factory {@Overridepublic ProductA createProductA() {return new ProductA2();}@Overridepublic ProductB createProductB() {return new ProductB2();}
}
package com.company.AbstractFactory;public class AbstractFactory {public static void main(String[] args) {Factory factory1 = new Factory1();ProductA productA = factory1.createProductA();productA.info();Factory factory2 = new Factory2();ProductB productB = factory2.createProductB();productB.info();}
}
适用性:
AbstractFactory 模式仅适用于:
1、一个系统要独立于它的产品的创建、组合和表示是。
2、一个系统要由多个产品系列中的一个来配置。
3、当要强调一系列相关的产品对象的设计以便进行联合使用时。
4、当提供一个产品类库,只想显示它们的接口而不是实现时。
4、原型模式(Prototype)
用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。
package com.company.Prototype;interface Prototype {public Object Clone();
}
package com.company.Prototype;class Product implements Prototype {private int id;private double price;public Product() {}public Product(int id, double price) {this.id = id;this.price = price;}public int getId() {return id;}public double getPrice() {return price;}@Overridepublic Object Clone() {Product object = new Product();object.id = this.id;object.price = this.price;return object;}
}
package com.company.Prototype;public class PrototypePattern {public static void main(String[] args) {Product product1 = new Product(2022, 5.28);System.out.println(product1.getId() + " " + product1.getPrice());// Product product2 = new Product(2022, 5.28);Product product2 = (Product) product1.Clone();System.out.println(product2.getId() + " " + product2.getPrice());Product product3 = (Product) product1.Clone();System.out.println(product3.getId() + " " + product3.getPrice());}
}
适用性
Prototype模式适用于:
1、当一个系统应该独立于它的产品创建、构成和表示时。
2、当要实例化的类是在运行时刻指定时,例如,通过动态加载。
3、为了避免创建一个与产品类层次平行的工厂类层次时。
4、当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们,可能比每次用合适的状态手工实例化该类更方便一些。
5、单例模式(Singleton)
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
package com.company.Singleton;class Singleton {private int number = 2022;public void setNumber(int number) {this.number = number;}public int getNumber() {return number;}private static Singleton instance = new Singleton();private Singleton() {}public static Singleton getInstance() {return instance;}
}
package com.company.Singleton;public class SingletonPattern {public static void main(String[] args) {// Singleton singleton1 = new Singleton();Singleton singleton1 = Singleton.getInstance();Singleton singleton2 = Singleton.getInstance();Singleton singleton3 = Singleton.getInstance();System.out.println(singleton1.getNumber() + " " + singleton2.getNumber() + " " + singleton3.getNumber());singleton1.setNumber(528);System.out.println(singleton1.getNumber() + " " + singleton2.getNumber() + " " + singleton3.getNumber());}
}
适用性
Singleton 模式适用于:
1、当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。
2、当这个唯一实例应该是通过子类化可扩展的,并且客户无须更改代码就能使用一个扩展的实例时。
6、适配器模式(Adapter)
将一个类的接口转换成客户希望的另外一个接口。Adapter模式是的原本由于接口不兼容而不能一起工作的那个类可以一起工作。
package com.company.Adapter;class USB {public void Request() {System.out.println("USB数据线");}
}
package com.company.Adapter;class TypeC {public void SpecificRequest() {System.out.println("Type-C数据线");}
}
package com.company.Adapter;class Adapter extends USB {private TypeC typeC = new TypeC();@Overridepublic void Request() {typeC.SpecificRequest();}
}
package com.company.Adapter;public class AdapterPattern {public static void main(String[] args) {USB usb = new Adapter();usb.Request();}
}
适用性
Adapter模式适用于:
1、想使用一个已经存在的类,而它的接口不符合要求。
2、想创建一个可以服用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作。
3、(仅适用于对象Adapter)想使用一个已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口。
7、桥接模式(Bridge)
将抽象的部分与其实现的部分分离,使它们都可以独立的变化。
package com.company.Bridge;interface Color {public void OperationImp(String name);
}
package com.company.Bridge;class Red implements Color {@Overridepublic void OperationImp(String name) {System.out.println(name + ":红色");}
}
package com.company.Bridge;class Blue implements Color {@Overridepublic void OperationImp(String name) {System.out.println(name + ":蓝色");}
}
package com.company.Bridge;abstract class Product {private String name;protected Color color;public void setName(String name) {this.name = name;}public String getName() {return name;}public void setColor(Color color) {this.color = color;}public abstract void Operation();
}
package com.company.Bridge;class ProductA extends Product {@Overridepublic void Operation() {color.OperationImp(this.getName());}
}
package com.company.Bridge;public class BridgePattern {public static void main(String[] args) {Product productA1 = new ProductA();Product productA2 = new ProductA();Color red = new Red();productA1.setName("产品A1");productA1.setColor(red);productA1.Operation();Blue blue = new Blue();productA2.setName("产品A2");productA2.setColor(blue);productA2.Operation();}
}
适用性
Bridge模式适用于:
1、不希望在抽象和它的实现部分之间有一个固定的绑定关系。例如,这种情况可能是因为,在程序运行时刻实现部分应可以被选择或者切换。
2、类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充。这是Bridge模式使得开发者可以对不同的抽象接口和实现部分进行组合,并分别对它们进行扩充。
3、对一个抽象的实现部分的修改应对客户不产生影响,即客户代码不必重新编译。
4、(C++)想对客户完全隐藏抽象的实现部分。
5、有许多类要生成的类层次结构。
6、想在多个对象间共享实现(可能使用引用计数),但同时要求客户并不知道这一点。
8、组合模式(Composite)
将对象组合成树型结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。
package com.company.CompositePattern;import java.util.List;abstract class AbstractFile {protected String name;public void printName() {System.out.println(name);}public abstract boolean Add(AbstractFile file);public abstract boolean Remove(AbstractFile file);public abstract List<AbstractFile> getChildren();
}
package com.company.CompositePattern;import java.util.ArrayList;
import java.util.List;class Folder extends AbstractFile {private List<AbstractFile> childrenList = new ArrayList<AbstractFile>();public Folder(String name) {this.name = name;}@Overridepublic boolean Add(AbstractFile file) {return childrenList.add(file);}@Overridepublic boolean Remove(AbstractFile file) {return childrenList.remove(file);}@Overridepublic List<AbstractFile> getChildren() {return childrenList;}
}
package com.company.CompositePattern;import java.util.List;class File extends AbstractFile {public File(String name) {this.name = name;}@Overridepublic boolean Add(AbstractFile file) {return false;}@Overridepublic boolean Remove(AbstractFile file) {return false;}@Overridepublic List<AbstractFile> getChildren() {return null;}
}
package com.company.CompositePattern;import java.util.List;public class CompositePattern {public static void main(String[] args) {// 父类名 对象名 = new 子类名();AbstractFile root = new Folder("root");AbstractFile folderA = new Folder("folderA");AbstractFile folderB = new Folder("folderB");AbstractFile fileC = new File("fileC");AbstractFile fileD = new File("fileD");AbstractFile fileE = new File("fileE");root.Add(folderA);root.Add(folderB);root.Add(fileC);folderA.Add(fileD);folderA.Add(fileE);print(root);}static void print(AbstractFile file) {file.printName();List<AbstractFile> childrenList = file.getChildren();if (childrenList == null) return;// for (对象类型 对象名 : 遍历对象)for (AbstractFile children : childrenList) {// children.printName();print(children);}}
}
适用性
Composite模型适用于:
1、相表示对象的部分-整体层次结构。
2、希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
9、装饰器模式(Decorator)
动态地给一个对象添加一些额外的职责。就增加功能而言,Decorator模式比生成子类更加灵活。
package com.company.DecoratorPattern;abstract class Person {protected String name;public abstract void Operation(); // 职责
}
package com.company.DecoratorPattern;class Student extends Person {public Student(String name) {this.name = name;}@Overridepublic void Operation() {System.out.print(name + "的职责:学习 ");}
}
package com.company.DecoratorPattern;abstract class Decorator extends Person {protected Person person;
}
package com.company.DecoratorPattern;class DecoratorA extends Decorator {public DecoratorA(Person person) {this.person = person;}@Overridepublic void Operation() { // 职责person.Operation(); // 原本的职责System.out.print("写作业 ");}
}
package com.company.DecoratorPattern;class DecoratorB extends Decorator {public DecoratorB(Person person) {this.person = person;}@Overridepublic void Operation() { // 职责person.Operation(); // 原本的职责System.out.print("考试 ");}
}
package com.company.DecoratorPattern;public class DecoratorPattern {public static void main(String[] args) {Person zhangsan = new Student("张三");zhangsan = new DecoratorA(zhangsan);zhangsan = new DecoratorB(zhangsan);zhangsan.Operation();System.out.println("\n=====我是分割线=====");// 对象链Person lisi = new DecoratorB(new DecoratorA(new Student("李四")));lisi.Operation();}
}
适用性
Decorator模式适用于:
1、在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
2、处理那些可以撤销的职责。
3、当不能采用生成子类的方式进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是,由于类定义被隐藏,或类定义不能用于生成子类。
10、外观模式(Facade)
为子系统中的一组接口提供一个一致的界面。Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
package com.company.FacadePattern;class SubSystemOne {public void methodOne() {System.out.println("执行子系统一的功能");}
}
package com.company.FacadePattern;class SubSystemTwo {public void methodTwo() {System.out.println("执行子系统二的功能");}
}
package com.company.FacadePattern;class SubSystemThree {public void methodThree() {System.out.println("执行子系统三的功能");}
}
package com.company.FacadePattern;class Facade {SubSystemOne subSystemOne;SubSystemTwo subSystemTwo;SubSystemThree subSystemThree;public Facade() {subSystemOne = new SubSystemOne();subSystemTwo = new SubSystemTwo();subSystemThree = new SubSystemThree();}public void methodA() {subSystemOne.methodOne();}public void methodB() {subSystemTwo.methodTwo();}public void methodC() {subSystemThree.methodThree();}
}
package com.company.FacadePattern;public class FacadePattern {public static void main(String[] args) {Facade facade = new Facade();facade.methodA();facade.methodB();facade.methodC();}
}
适用性
Facade模式适用于:
1、要为一个复杂子系统提供一个简单接口时,子系统往往因为不断演化而变得越来越复杂,大多数模式使用时都会产生更多更小的类,这使得子系统更具有可重用性,也更容易对子系统进行定制,但也给那些不需要定制子系统的用户带来一些使用上的困难。Facade可以提供一个简单的默认视图,这一视图对大多数用户来说已经足够,而那些需要更多的可定制行的用户可以越过Facade层。
2、客户程序与抽象类的实现部分之间存在着很大的依赖性。引入Facade将这个子系统与客户以及其他子系统分离,可以提高子系统的独立性和可移植性。
3、当需要构建一个层次结构的子系统时,使用Facade模式定义子系统中每层的入口点。如果子系统之间是相互依赖的,则可以让他们仅通过Facade进行通信,从而简化了它们之间的依赖关系。
11、享元模式(Flyweight)
运用共享技术有效地支持大量细粒度的对象。
package com.company.FlyWeightPattern;abstract class Shape {protected String color;public abstract void draw(int x, int y);
}
package com.company.FlyWeightPattern;class Circle extends Shape {public Circle(String color) {this.color = color;}@Overridepublic void draw(int x, int y) {System.out.println("draw a color:" + color + " circle x:" + x + " y:" + y);}
}
package com.company.FlyWeightPattern;import java.util.HashMap;
import java.util.Map;class ShapeFactory {private Map<String, Shape> map = new HashMap<String, Shape>();public Shape getShape(String key) {if (!map.containsKey(key)) {map.put(key, new Circle(key));System.out.println("create color:" + key + " circle");}return map.get(key);}
}
package com.company.FlyWeightPattern;import java.util.Random;public class FlyWeightPattern {public static void main(String[] args) {ShapeFactory factory = new ShapeFactory();Random random = new Random();String[] colors = {"red", "blue", "green", "white", "black"};for (int i = 1; i <= 100; i++) {int x = random.nextInt(colors.length); // [0 ~ 4]Shape shape = factory.getShape(colors[x]);System.out.print("第" + i + "个圆:");shape.draw(random.nextInt(2022), random.nextInt(528));}}
}
适用性
Flyweight模式适用于:
1、一个应用程序使用了大量的对象。
2、完全由于使用大量的对象,造成很大的存储开销。
3、对象的大多数状态都可变为外部状态。
4、如果删除对象的外部状态,那么可以用相对教授的共享对象取代很多组对象。
5、用用程序不依赖于对象标识。由于Flyweight对象可以被共享,所以对于概念上明显有别的对象,标识测试将返回真值。
12、代理模式(Proxy)
为其他对象提供一种代理以控制对这个对象的访问。
package com.company.ProxyPattern;interface Subject {public void buy();
}
package com.company.ProxyPattern;class RealSubject implements Subject {@Overridepublic void buy() {System.out.println("付钱");}
}
class Proxy implements Subject {protected RealSubject realSubject;public Proxy(RealSubject realSubject) {this.realSubject = realSubject;}@Overridepublic void buy() {System.out.println("办理购买前的手续");realSubject.buy(); // 付钱System.out.println("办理购买后的手续");}
}
package com.company.ProxyPattern;public class ProxyPattern {public static void main(String[] args) {RealSubject realSubject = new RealSubject();Proxy proxy = new Proxy(realSubject);proxy.buy();}
}
适用性:
Proxy模式适用于在需要比较通用和复杂的对象指针代替简单的指针的时候,常见情况有:
1、远程代理(Remote Proxy)为一个对象在不同地址空间提供局部代表。
2、虚代理(Virtual Proxy)根据需要创建开销很大的对象。
3、保护代理(Protection Proxy)控制对原始对象的访问,用于对象应该有不同的访问权限的时候。
4、智能引用(Smart Reference)取代了简单的指针,它在访问对象时执行一些附加操作。典型用途包括:对指向实际对象的引用计数,这样当该对象没有引用时,可以被自动释放;当第一次引用一个持久对象时,将它装入内存;在访问一个实际对象前,检查是否已经锁定了它,以确保其他对象不能改变它。
13、责任链模式(Chain of Responsibility)
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
package com.company.ChainOfResponsibilityPattern;abstract class Handler {protected Handler next;public void setNext(Handler next) {this.next = next;}public abstract void HandlerRequest(int request);
}
package com.company.ChainOfResponsibilityPattern;class FuDaoYuan extends Handler { // <= 7 审批@Overridepublic void HandlerRequest(int request) {if (request <= 7) {System.out.println("辅导员审批通过");} else {if (next != null) {next.HandlerRequest(request);} else {System.out.println("无法审批");}}}
}
package com.company.ChainOfResponsibilityPattern;class YuanZhang extends Handler { // <= 15 审批@Overridepublic void HandlerRequest(int request) {if (request <= 15) {System.out.println("院长审批通过");} else {if (next != null) {next.HandlerRequest(request);} else {System.out.println("无法审批");}}}
}
package com.company.ChainOfResponsibilityPattern;class XiaoZhang extends Handler { // <= 30 审批@Overridepublic void HandlerRequest(int request) {if (request <= 30) {System.out.println("校长审批通过");} else {if (next != null) {next.HandlerRequest(request);} else {System.out.println("无法审批");}}}
}
package com.company.ChainOfResponsibilityPattern;public class ChainOfResponsibilityPattern {public static void main(String[] args) {Handler fudaoyuan = new FuDaoYuan();Handler yuanzhang = new YuanZhang();Handler xiaozhang = new XiaoZhang();fudaoyuan.setNext(yuanzhang);yuanzhang.setNext(xiaozhang);fudaoyuan.HandlerRequest(31);}
}
适用性
Chain of Responsibility模式适用于以下条件:
1、有多个对象可以处理一个请求,哪个对象处理该请求运行时刻自动确定。
2、想在不明确指定接收者的情况下向多个对象中的一个提交一个请求。
3、可处理一个请求的对象集合应被动态指定。
14、命令模式(Command)
将一个请求封装为一个对象,从而使得可以用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
package com.company.CommandPattern;class Tv { // 接收者 电视机public void OnAction() { // 开机行为System.out.println("电视机开机了...");}public void OffAction() { // 关机行为System.out.println("电视机关机了...");}
}
package com.company.CommandPattern;interface Command { // 命令接口public void Execute(); // 执行命令
}
package com.company.CommandPattern;class Invoker { // 请求者private Command command; // 命令public void setCommand(Command command) { // 设置请求者 的 请求的命令this.command = command;}public void call() { // 调用command.Execute();}
}
package com.company.CommandPattern;class OnCommand implements Command { // 开机命令private Tv tv;public OnCommand(Tv tv) {this.tv = tv;}@Overridepublic void Execute() {tv.OnAction();}
}
package com.company.CommandPattern;class OffCommand implements Command { // 关机命令private Tv tv;public OffCommand(Tv tv) {this.tv = tv;}@Overridepublic void Execute() {tv.OffAction();}
}
package com.company.CommandPattern;public class CommandPattern {public static void main(String[] args) {Tv tv = new Tv(); // 接收者 对象 电视机Command onCommand = new OnCommand(tv); // 命令对象 开机命令Command offCommand = new OffCommand(tv); // 命令对象 关机命令Invoker invoker = new Invoker(); // 请求者invoker.setCommand(onCommand); // 给请求者设置 开机 命令invoker.call(); // 请求者去请求命令System.out.println("========================================");invoker.setCommand(offCommand); // 给请求者设置 关机命令invoker.call(); // 请求者去请求命令}
}
适用性
Command模式适用于:
1、抽象出待执行的动作以参数化某对象。Command模式是过程语言中的回调(Callback)机制的一个面向对象的替代品。
2、在不同的时刻指定、配列和执行请求。一个Command对象可以有一个与初始请求无关的生存期。如果一个请求的接收者可用一种与地址空间无关的方式表达,那么就可以将负责该请求的命令对象传递给另一个不同的进程并在那儿实现该请求。
3、支持取消操作。Command的Execute操作可在实施操作前将状态存储起来,在取消操作时这个状态用来消除该操作的影响。Command接口必须添加一个Unexecute操作,该操作取消上一次Execute调用的效果。执行的命令被存储在一个历史列表中。可通过向后和向前遍历这一列表并分别调用Unexecute和Execute来实现重数不限的“取消”和“重做”。
4、支持修改日志。这样当系统奔溃时,这些修改可以被重做一遍。在Command接口中添加装载操作和存储操作,可以用来保持变动的一个一致的修改日志。从奔溃中恢复的过程包括从磁盘中重新读入记录下来的命令并用Execute操作重新执行它们。
5、用构建在原语操作上的高层操作构造一个系统。这样一中结构在支持事务(Transaction)的信息系统中很常见。Command模式提供了对事务进行建模的方法。Command有一个公共接口,使得可以用同一种方式调用所有的事务,同时使用该模式也易于添加新事务以扩展系统。
15、解释器模式(Interpreter)
给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
package com.company.InterpreterPattern;interface Expression {public boolean Interpret(String info);
}
package com.company.InterpreterPattern;import java.util.HashSet;
import java.util.Set;class TerminalExpression implements Expression {private Set<String> set = new HashSet<>();public TerminalExpression(String[] data) {// for (遍历对象类型 对象名 : 遍历对象)for (String str : data) {set.add(str);}}@Overridepublic boolean Interpret(String info) {return set.contains(info);}
}
package com.company.InterpreterPattern;class NonterminalExprssion implements Expression {private TerminalExpression region;private TerminalExpression person;public NonterminalExprssion(TerminalExpression region, TerminalExpression person) {this.region = region;this.person = person;}@Overridepublic boolean Interpret(String info) {String[] str = info.split("的");// B区的调试人员 --> str = {"B区", "调试人员"}return region.Interpret(str[0]) && person.Interpret(str[1]);}
}
package com.company.InterpreterPattern;class Context {private String[] regions = {"A区", "B区", "C区"};private String[] persons = {"开发人员", "测试人员", "调试人员"};private NonterminalExprssion nonterminal;public Context() {TerminalExpression region = new TerminalExpression(regions);TerminalExpression person = new TerminalExpression(persons);nonterminal = new NonterminalExprssion(region, person);}public void check(String info) {boolean bool = nonterminal.Interpret(info);if (bool) {System.out.println("识别成功");} else {System.out.println("识别失败");}}
}
package com.company.InterpreterPattern;public class InterpreterPattern {public static void main(String[] args) {Context context = new Context();context.check("A区的开发人员");context.check("B区的调试人员");context.check("C区的测试人员");System.out.println("==========");context.check("D区的程序员");context.check("D区的测试员");context.check("A区的程序员");}
}
适用性
Interpreter模式适用于当有一个语言需要解释执行,并且可将该语言中的句子表示为一个抽象语法树时,以下情况效果最好:
1、该文法简单。对于复杂的发文,文法的类层次变得庞大而无法管理。此时语法分析程序生成器这样的工具是更好的选择。它们无须构建抽象语法树即可解释表达式,这样可以节省空间还可能节省时间。
2、效率不是一个关键问题。最高效的解释器通常不是通过直接解释语法分析树实现的,而是首先将它们转换成另一种形式。不过,即使在这种情况下,转换器仍然可用该模式实现。
16、迭代器模式(Iterator)
提供一种方法顺序访问一个聚合集合对象中的各个元素,而不需要暴露该对象的内部表示。
package com.company.IteratorPattern;interface Aggregate {public Iterator CreateIterator();
}
package com.company.IteratorPattern;interface Iterator {public boolean hasNext();public Object next();
}
package com.company.IteratorPattern;class Book {private String name;private double price;public Book(String name, double price) {this.name = name;this.price = price;}public String getName() {return name;}public double getPrice() {return price;}
}
package com.company.IteratorPattern;import java.util.ArrayList;
import java.util.List;class BookAggregate implements Aggregate {private List<Book> list = new ArrayList<Book>();public void Add(Book book) {list.add(book);}public Book get(int index) {return list.get(index);}public int getSize() {return list.size();}@Overridepublic Iterator CreateIterator() {return new BookIterator(this);}
}
package com.company.IteratorPattern;class BookIterator implements Iterator {private int index;private BookAggregate bookAggregate;public BookIterator(BookAggregate bookAggregate) {this.index = 0;this.bookAggregate = bookAggregate;}@Overridepublic boolean hasNext() {if (index < bookAggregate.getSize()) {return true;} else {return false;}}@Overridepublic Object next() {Object obj = bookAggregate.get(index);index++;return obj;}
}
package com.company.IteratorPattern;public class IteratorPattern {public static void main(String[] args) {BookAggregate bookAggregate = new BookAggregate();String[] books = {"数据结构", "操作系统", "计算机网络", "计算机组成原理"};double[] prices = {10.24, 20.48, 40.96, 81.92};for (int i = 0; i < 4; i++) {bookAggregate.Add(new Book(books[i], prices[i]));}Iterator bookIterator = bookAggregate.CreateIterator();while (bookIterator.hasNext()) {Book book = (Book) bookIterator.next();System.out.println(book.getName() + " " + book.getPrice());}}
}
适用于
Iterator模式适用于:
1、访问一个聚合对象的内容而无须暴露它的内部展示。
2、支持对聚合对象的多种遍历。
3、为遍历不同的聚合结构提供一个统一的接口。
17、中介者模式(Mediator)
用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散。而且可以独立地改变它们之间的交互。
package com.company.MediatorPattern;abstract class Colleague {protected Mediator mediator;
}
package com.company.MediatorPattern;abstract class Mediator {public abstract void sendMessage(String message, Colleague colleague);
}
package com.company.MediatorPattern;class Colleague1 extends Colleague {public Colleague1(Mediator mediator) {this.mediator = mediator;}public void sendMessage(String message) {mediator.sendMessage(message, this);}public void Notify(String message) {System.out.println("同事1收到消息:" + message);}
}
package com.company.MediatorPattern;class Colleague2 extends Colleague {public Colleague2(Mediator mediator) {this.mediator = mediator;}public void sendMessage(String message) {mediator.sendMessage(message, this);}public void Notify(String message) {System.out.println("同事2收到消息:" + message);}
}
package com.company.MediatorPattern;class ConcreteMediator extends Mediator {private Colleague1 colleague1;private Colleague2 colleague2;public void setColleague1(Colleague1 colleague1) {this.colleague1 = colleague1;}public void setColleague2(Colleague2 colleague2) {this.colleague2 = colleague2;}public void sendMessage(String message, Colleague colleague) {if (colleague == colleague1) {colleague2.Notify(message); // 让同事2收到消息} else {colleague1.Notify(message); // 让同事1收到消息}}
}
package com.company.MediatorPattern;public class MediatorPattern {public static void main(String[] args) {ConcreteMediator mediator = new ConcreteMediator();Colleague1 colleague1 = new Colleague1(mediator);Colleague2 colleague2 = new Colleague2(mediator);mediator.setColleague1(colleague1);mediator.setColleague2(colleague2);colleague1.sendMessage("软考加油");colleague2.sendMessage("祝大家软考顺利通过!");}
}
适用性
Mediator模式适用于:
1、一组对象以定义良好但是复杂的方式进行通信,产生的相互依赖关系结构混乱且难以理解。
2、一个对象引用其他很多对象并且直接与这些对象通信,导致难以复用该对象。
3、想定制一个分布在多个类中的行为,而又不想生成太多的子类。
18、备忘录模式(Memento)
在不破坏封装性的前提下捕获一个对象的内部状态,并在对象之外保存这个状态。这样以后就可以将对象恢复到原先保存的状态。
package com.company.MementoPattern;class Memento { // 备忘录private String state;public Memento(String state) {this.state = state;}public String getState() {return state;}
}
package com.company.MementoPattern;class Originator { // 原发器private String state;public void setState(String state) {this.state = state;}public String getState() {return state;}public Memento createMemento() {return new Memento(state);}public void setMemento(Memento memento) {state = memento.getState();}
}
package com.company.MementoPattern;import java.util.ArrayList;
import java.util.List;class Caretaker { // 管理者private List<Memento> mementoList = new ArrayList<>();public void addMemento(Memento memento) {mementoList.add(memento);}public Memento getMemento(int index) {// 判断参数是否合法if (index >= 1 && index <= mementoList.size()) {return mementoList.get(index - 1);}return null;}public void showMemento() {int cnt = 1;// for (遍历对象类型 对象名 : 遍历对象)for (Memento memento : mementoList) {System.out.println("第" + cnt + "次备份,状态为:" + memento.getState());cnt++;}}
}
package com.company.MementoPattern;public class MementoPattern {public static void main(String[] args) {Caretaker caretaker = new Caretaker();Originator originator = new Originator();originator.setState("1024");Memento backup1 = originator.createMemento();caretaker.addMemento(backup1);originator.setState("2048");Memento backup2 = originator.createMemento();caretaker.addMemento(backup2);originator.setState("4096");Memento backup3 = originator.createMemento();caretaker.addMemento(backup3);System.out.println(originator.getState());caretaker.showMemento();Memento memento1 = caretaker.getMemento(2);originator.setMemento(memento1);System.out.println("根据第2次备份还原之后的状态为:" + originator.getState());}
}
适用性
Memento模式适用于:
1、必须保存一个对象在某一个时刻的(部分)状态,这样以后需要时他才能恢复到先前的状态。
2、如果一个用接口来让其他对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性。
19、观察者模式(Observer)
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
package com.company.ObserverPattern;interface Subject { // 目标public void Attach(Observer observer); // 添加观察者public void Detach(Observer observer); // 删除观察者public void Notify(); // 状态改变后 通知所有观察者public void setState(String state); // 设置状态(改变状态)public String getState(); // 获取状态
}
package com.company.ObserverPattern;interface Observer { // 观察者接口public void update(); // 收到通知 更新观察者的状态
}
package com.company.ObserverPattern;import java.util.ArrayList;
import java.util.List;class ConcreteSubject implements Subject {private String name;private String state;private List<Observer> observerList;public ConcreteSubject(String name) {state = "未更新";this.name = name;observerList = new ArrayList<Observer>();}public void setState(String state) {this.state = state;System.out.println(name + "的状态发生变化,变化后的状态为:" + state);Notify();}public String getState() {return state;}public void Attach(Observer observer) {observerList.add(observer);}public void Detach(Observer observer) {observerList.remove(observer);}public void Notify() {// for (遍历对象类型 对象名 : 遍历对象)for (Observer observer : observerList) {observer.update();}}
}
package com.company.ObserverPattern;class ConcreteObserver implements Observer {private String name;private String state;private Subject subject;public ConcreteObserver(String name, Subject subject) {this.name = name;this.subject = subject;subject.Attach(this);state = subject.getState();}@Overridepublic void update() {System.out.println(name + "收到通知");state = subject.getState(); // 让当前观察者的状态 和 改变了状态之后的目标的状态保持一致System.out.println(name + "改变后的状态为:" + state);}
}
package com.company.ObserverPattern;public class ObserverPattern {public static void main(String[] args) {Subject subjectA = new ConcreteSubject("目标A");Observer observerB = new ConcreteObserver("张三", subjectA);Observer observerC = new ConcreteObserver("李四", subjectA);Observer observerD = new ConcreteObserver("王五", subjectA);subjectA.setState("更新了");System.out.println("======================================");subjectA.Detach(observerD);subjectA.setState("停更了");}
}
适用性
Observer模式适用于:
1、当一个抽象模型有两个方面,其中一个方面依赖于另一个方面,将这两者封装在独立的对象中以使他们可以各自独立的改变和服用。
2、当对一个对象的改变需要同时改变其他对象,而不知道具体有多少对象有待改变时。
3、当一个对象必须通知其他对象,而它又不能假定其他对象是谁,即不希望这些对象是禁耦合的。
20、状态模式(State)
允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
package com.company.StatePattern;class Context { // 贩卖机private int count;private State state;public Context() {count = 3;state = new StateA();}public int getCount() {return count;}public void setCount(int count) {this.count = count;}public State getState() {return state;}public void setState(State state) {this.state = state;}public void Request() { // 购买一个饮料state.Handle(this);}}
package com.company.StatePattern;interface State {public void Handle(Context context);
}
package com.company.StatePattern;class StateA implements State { // 有货@Overridepublic void Handle(Context context) {int count = context.getCount();if (count >= 1) {System.out.println("购买成功!");context.setCount(count - 1);if (context.getCount() == 0) {context.setState(new StateB());}} else {System.out.println("购买失败!");}}
}
package com.company.StatePattern;class StateB implements State { // 无货@Overridepublic void Handle(Context context) {int count = context.getCount();if (count == 0) {System.out.println("购买失败!等待补货");context.setCount(5);System.out.println("补货成功,请重新购买");context.setState(new StateA());}}
}
package com.company.StatePattern;public class StatePattern {public static void main(String[] args) {Context context = new Context(); // count:3System.out.println(context.getState());context.Request(); // 购买一个饮料 count = 2context.Request(); // 购买一个饮料 count = 1context.Request(); // 购买一个饮料 count = 0System.out.println(context.getState());context.Request(); // 无货 等待补货 补货成功 count = 5System.out.println(context.getState());context.Request(); // 购买一个饮料 count = 4System.out.println(context.getCount());}
}
适用性
State模式适用于:
1、一个对象的行为决定于它的状态,并且它必须在运行时刻根据状态改变它的行为。
2、一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。这个状态常用一个或多个枚举常量表示。通常,有多个操作包含这一相同的条件结构。State模式将每一个条件分支放入一个独立的类中。这使得开发者可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象独立变化。
21、策略模式(Strategy)
在策略模式(Strategy Pattern)中一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。
在策略模式定义了一系列算法或策略,并将每个算法封装在独立的类中,使得它们可以互相替换。通过使用策略模式,可以在运行时根据需要选择不同的算法,而不需要修改客户端代码。
在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。
package com.company.StrategyPattern;interface Strategy {public void TwoNumberOperation(int a, int b);
}
package com.company.StrategyPattern;public class OperationContext {private Strategy strategy;public OperationContext(Strategy strategy) {this.strategy = strategy;}public void Operation(int a, int b) {strategy.TwoNumberOperation(a, b);}
}
package com.company.StrategyPattern;class AddStrategy implements Strategy {@Overridepublic void TwoNumberOperation(int a, int b) {System.out.println(a + b);}
}
package com.company.StrategyPattern;class SubtractionStrategy implements Strategy {@Overridepublic void TwoNumberOperation(int a, int b) {System.out.println(a - b);}
}
package com.company.StrategyPattern;class MultiplyStrategy implements Strategy {@Overridepublic void TwoNumberOperation(int a, int b) {System.out.println(a * b);}
}
package com.company.StrategyPattern;public class StrategyPattern {public static void main(String[] args) {Strategy add = new AddStrategy();Strategy subtraction = new SubtractionStrategy();Strategy multiply = new MultiplyStrategy();OperationContext context = new OperationContext(add);context.Operation(2022, 528);context = new OperationContext(subtraction);context.Operation(2022, 528);context = new OperationContext(multiply);context.Operation(2022, 528);}
}
应用实例
-
锦囊妙计:每个锦囊代表一个策略,包含不同的计策。
-
旅行方式选择:骑自行车、坐汽车等,每种方式都是一个可替换的策略。
-
Java AWT的LayoutManager:不同的布局管理器实现了相同的接口,但提供了不同的布局算法。
优点
-
算法切换自由:可以在运行时根据需要切换算法。
-
避免多重条件判断:消除了复杂的条件语句。
-
扩展性好:新增算法只需新增一个策略类,无需修改现有代码。
缺点
-
策略类数量增多:每增加一个算法,就需要增加一个策略类。
-
所有策略类都需要暴露:策略类需要对外公开,以便可以被选择和使用。
使用建议
-
当系统中有多种算法或行为,且它们之间可以相互替换时,使用策略模式。
-
当系统需要动态选择算法时,策略模式是一个合适的选择。
注意事项
-
如果系统中策略类数量过多,考虑使用其他模式或设计技巧来解决类膨胀问题。
结构
策略模式包含以下几个核心角色:
-
环境(Context):维护一个对策略对象的引用,负责将客户端请求委派给具体的策略对象执行。环境类可以通过依赖注入、简单工厂等方式来获取具体策略对象。
-
抽象策略(Abstract Strategy):定义了策略对象的公共接口或抽象类,规定了具体策略类必须实现的方法。
-
具体策略(Concrete Strategy):实现了抽象策略定义的接口或抽象类,包含了具体的算法实现。
策略模式通过将算法与使用算法的代码解耦,提供了一种动态选择不同算法的方法。客户端代码不需要知道具体的算法细节,而是通过调用环境类来使用所选择的策略。
22、模版方法模式(Template Method)
定义一个操作中的算法骨架,而将一些步骤延迟到子类中,Template Method使得子类可以不改变一个算法结构即可重定义该算法的某些特定步骤。
package com.company.TemplateMethodPattern;abstract class Person {public void TemplateMethod() {System.out.println("上课 去教室"); // 1PrimitiveOperation1(); // 2System.out.println("下课 离开教室"); // 3PrimitiveOperation2(); // 4}public abstract void PrimitiveOperation1(); // 原语操作 1 :上课过程 学生 听课…… 老师 讲课public abstract void PrimitiveOperation2(); // 原语操作 2 :作业 学生 写作业 提交作业…… 老师 批改作业 打分数
}
package com.company.TemplateMethodPattern;class Student extends Person {@Overridepublic void PrimitiveOperation1() {System.out.println("学生:听课 学习 做笔记 提出问题");}@Overridepublic void PrimitiveOperation2() {System.out.println("学生:写作业 提交作业");}
}
package com.company.TemplateMethodPattern;class Teacher extends Person {@Overridepublic void PrimitiveOperation1() {System.out.println("老师:上课 讲课 解答问题 布置作业");}@Overridepublic void PrimitiveOperation2() {System.out.println("老师:批改作业 打分数");}
}
package com.company.TemplateMethodPattern;public class TemplateMethodPattern {public static void main(String[] args) {// 父类名 对象名 = new 子类名();Person student = new Student();Person teacher = new Teacher();student.TemplateMethod();System.out.println("=====我是分割线=====");teacher.TemplateMethod();}
}
适用性
Template Method模式适用于:
1、一次性实现一个算法的不变部分,并将可变的行为留给子类来实现。
2、各子类中公共的行为应被提取出来并集中到一个公共的父类中,以避免代码重复。
3、控制子类扩展。模版方法旨在特定点调用“hook”操作(默认的行为,子类可以再必要时进行重定义扩展),这就只允许在这些点进行扩展。
23、访问者模式(Visitor)
表示一个作用于某对象结构中的各个元素的操作。它允许在不改变各元素的类的前提下定义作用于这些元素的新操作。
package com.company.VisitorPattern;abstract class Person {private String name;private int age;public Person(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public int getAge() {return age;}public abstract void Accept(Visitor visitor);
}
package com.company.VisitorPattern;class Student extends Person {private int score;public Student(String name, int age, int score) {super(name, age);this.score = score;}public int getScore() {return score;}@Overridepublic void Accept(Visitor visitor) {visitor.visitStudent(this);}
}
package com.company.VisitorPattern;class Teacher extends Person {private int workYear;public Teacher(String name, int age, int workYear) {super(name, age);this.workYear = workYear;}public int getWorkYear() {return workYear;}@Overridepublic void Accept(Visitor visitor) {visitor.visitTeacher(this);}
}
package com.company.VisitorPattern;import java.util.ArrayList;
import java.util.List;class PersonStructure {private List<Person> personList = new ArrayList<Person>();public PersonStructure() {personList.add(new Student("张三", 20, 70));personList.add(new Student("李四", 21, 80));personList.add(new Student("王五", 22, 90));personList.add(new Teacher("李老师", 26, 3));personList.add(new Teacher("陈老师", 27, 4));personList.add(new Teacher("刘老师", 28, 5));}public void Accept(Visitor visitor) {// for (遍历对象类型 对象名 : 遍历对象)for (Person person : personList) {person.Accept(visitor);}}
}
package com.company.VisitorPattern;interface Visitor {public void visitStudent(Student student); // 访问学生public void visitTeacher(Teacher teacher); // 访问老师
}
package com.company.VisitorPattern;class Visitor1 implements Visitor { // 访问者1 分别统计学生和老师的年龄总和private int studentAgeSum = 0;private int teacherAgeSum = 0;public int getStudentAgeSum() {return studentAgeSum;}public int getTeacherAgeSum() {return teacherAgeSum;}@Overridepublic void visitStudent(Student student) {System.out.println("访问者1访问学生:" + student.getName() + " 年龄:" + student.getAge());studentAgeSum += student.getAge();}@Overridepublic void visitTeacher(Teacher teacher) {System.out.println("访问者1访问老师:" + teacher.getName() + " 年龄:" + teacher.getAge());teacherAgeSum += teacher.getAge();}
}
package com.company.VisitorPattern;class Visitor2 implements Visitor { // 访问者2 分别求出 学生的最高成绩 以及 老师的最高工龄private int maxScore = -1;private int maxWorkYear = -1;public int getMaxScore() {return maxScore;}public int getMaxWorkYear() {return maxWorkYear;}@Overridepublic void visitStudent(Student student) {System.out.println("访问者2访问学生:" + student.getName() + " 成绩:" + student.getScore());maxScore = Math.max(maxScore, student.getScore());}@Overridepublic void visitTeacher(Teacher teacher) {System.out.println("访问者2访问老师:" + teacher.getName() + " 工龄:" + teacher.getWorkYear());maxWorkYear = Math.max(maxWorkYear, teacher.getWorkYear());}
}
package com.company.VisitorPattern;public class VisitorPattern {public static void main(String[] args) {PersonStructure structure = new PersonStructure();Visitor1 visitor1 = new Visitor1();System.out.println("访问者1的访问记录:");structure.Accept(visitor1);System.out.println("学生年龄的总和:" + visitor1.getStudentAgeSum() + " 老师年龄的总和:" + visitor1.getTeacherAgeSum());System.out.println("=========================================");Visitor2 visitor2 = new Visitor2();System.out.println("访问者2的访问记录:");structure.Accept(visitor2);System.out.println("学生的最高成绩:" + visitor2.getMaxScore() + " 老师的最高工龄:" + visitor2.getMaxWorkYear());}
}
适用性
Visitor模式适用于:
1、一个对象结构包含很多类对象,它们有不同的接口,而用户想对这些对象实施一些依赖于其具体类的操作。
2、需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而又想要避免这些操作“污染”这些对象的类。Visitor使得用户可以将相关的操作集中起来定义在一个类中。当该对象结构被很多应用共享时,用Visitor模式让每个应用仅包含需要用到的操作。
3、定义对象结构的类很少改变,但经常需要在次结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这可能需要很大的代价。如果对象结构类经常改变,那么可能还是在这些类中定义这些操作比较好。
相关文章:
软件设计师之设计模式
设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。…...
洛谷题单3-P1720 月落乌啼算钱(斐波那契数列)-python-流程图重构
题目描述 给定一个整数 N N N,请将该数各个位上数字反转得到一个新数。新数也应满足整数的常见形式,即除非给定的原数为零,否则反转后得到的新数的最高位数字不应为零(参见样例 2)。 输入格式 一个整数 N N N。 …...
WinForm真入门(5)——控件的基类Control
控件的基类–Control 用于 Windows 窗体应用程序的控件都派生自 Control类并继承了许多通用成员,这些成员都是平时使用控件的过程最常用到的。无论要学习哪个控件的使用,都离不开这些基本成员,尤其是一些公共属性。由于 Conlrol 类规范了控件的基本特征…...
第一讲—函数的极限与连续(一)
思维导图 笔记 双曲正弦函数及其反函数...
开发一个项目的顺序
目录 1.设计表 2.写好pom.xml和application.yml文件 (设置端口号,配置数据源) 3.引入一个插件,帮助自动生成dao层,model层和mapper目录的代码 4.接着配置mybatis的扫描路径,产生这些文件后,…...
第P10周:Pytorch实现车牌识别
🍨 本文为🔗365天深度学习训练营中的学习记录博客 🍖 原作者:K同学啊 一.导入数据 from torchvision.transforms import transforms from torch.utils.data import DataLoader from torchvision import datase…...
如何在 Windows 上安装 Python
Python是一种高级编程语言,由于其简单性、多功能性和广泛的应用范围而变得越来越流行。如何在 Windows 操作系统中安装 Python 的过程相对简单,只需几个简单的步骤。 本文旨在指导您完成在 Windows 计算机上下载和安装 Python 的过程。 如何在 Windows…...
探秘区块链开发:智能合约在 DApp 中的地位及与传统开发差异
从:引言:当我们谈论区块链开发时,实际在讨论什么?,我们已经能够知道,当我们在讨论区块链开发的时候,大多数时间里说的就是DApp开发。 那么DApp是由什么组成的呢?从上篇文章的特征中我们得出一个技术名词”智能合约“。这是DApp的一个重要特征,也是DApp的一个重要组成…...
react redux的学习,多个reducer
redux系列文章目录 第一章 简单学习redux,单个reducer 前言 前面我们学习到的是单reducer的使用;要知道redux是个很强大的状态存储库,可以支持多个reducer的使用。 combineReducers combineReducers是Redux中的一个辅助函数,主要用于…...
SadTalker 数字人web网页版-不需要GPU也可以跑
数字人启动 Active code page: 65001 开始运行 Python 3.10.11 (tags/v3.10.11:7d4cc5a, Apr 5 2023, 00:38:17) [MSC v.1929 64 bit (AMD64)] Commit hash: <none> Installing requirements for SadTalker WebUI (may take longer time in first time) Launching SadT…...
最少刷题数--二分+排序
1.考虑重复,题意是多的不超过少的,等于不算 2.所以中间的要二分判断 3.同时排序后要刷的题数也可能是pos-i,也可能是pos-i1,也要判断一下 #include<bits/stdc.h> using namespace std; #define N 100011 typedef long lo…...
花卉识别分类系统,Python/resnet18/pytorch
花卉识别分类系统,Python/resnet18/pytorch 基于pytorch训练, resnet18网络,可用于训练其他分类问题,也可自己重新训练 共五种花卉:雏菊,蒲公英,玫瑰,向日葵,郁金香 标价包含GUI源码、数据集…...
基于 .NET 8 + Lucene.Net + 结巴分词实现全文检索与匹配度打分实战指南
文章目录 前言一、技术选型与优势1.1 技术栈介绍1.2 方案优势 二、环境搭建与配置2.1 安装 NuGet 包2.2 初始化核心组件 三、索引创建与文档管理3.1 构建索引3.2 动态更新策略 四、搜索与匹配度排序4.1 执行搜索4.2 自定义评分算法(扩展) 五、高级优化技…...
【图像处理基石】什么是neural style transfer?
1. 什么是neural style transfer? 神经风格迁移(Neural Style Transfer)是一种利用深度学习技术将一幅图像的风格(如笔触、色彩、纹理等)与另一幅图像的内容(如物体、场景结构)结合的方法。其核心思想是通…...
ubuntu20.04升级成ubuntu22.04
命令行 sudo do-release-upgrade 我是按提示输入y确认操作,也可以遇到配置文件冲突时建议选择N保留当前配置...
【C++奇遇记】C++中的进阶知识(继承(一))
🎬 博客主页:博主链接 🎥 本文由 M malloc 原创,首发于 CSDN🙉 🎄 学习专栏推荐:LeetCode刷题集 数据库专栏 初阶数据结构 🏅 欢迎点赞 👍 收藏 ⭐留言 📝 如…...
SpringBoot异步任务实践指南:提升系统性能的利器
精心整理了最新的面试资料和简历模板,有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 引言 在现代Web应用中,高并发场景下的响应速度和资源利用率是系统设计的重要考量。SpringBoot通过简洁的异步任务机制,帮助开发者轻松…...
Gson修仙指南:谷歌大法的佛系JSON渡劫手册
各位在代码世界打坐修行的道友们!今天我们要参悟Google出品的JSON心法——Gson!这货就像代码界的扫地僧,表面朴实无华,实则内力深厚,专治各种JSON不服!准备好迎接"万物皆可JSON"的顿悟时刻了吗&a…...
MINIQMT学习课程Day8
获取qmt账号的资金账号后,我们进入下一步,如何获得当前账号的持仓情况 还是之前的步骤,打开qmt,选择独立交易, 之后使用pycharm,编写py文件。 from xtquant import xtdata from xtquant.xttrader import…...
spring-ai-alibaba第八章使用searxng构建大模型联网搜索应用
1、searxng安装配置 详见 anythingLLM结合searXNG实现联网搜索_anythingllm 配置 searxng-CSDN博客 2、本文介绍如何使用 Spring AI Alibaba 构建大模型联网搜索应用结合模块化 RAG(Module RAG)和信息检索服务(SearXNG)赋能大模…...
C#:is关键字
目录 is 关键字的核心是什么? 1. 什么是 is 关键字,为什么要用它? 2. 如何使用 is 关键字? 3. is 的作用和场景 4. is 与 as 的区别 5. 模式匹配的扩展(C# 8.0) 6. 常见陷阱和注意事项 总结&#x…...
SpringCloud第二篇:注册中心Eureka
注册中心的意义 注册中心 管理各种服务功能包括服务的注册、发现、熔断、负载、降级等,比如dubbo admin后台的各种功能。 有了注册中心,调用关系的变化,画几个简图来看一下。(了解源码可求求: 1791743380) 服务A调用服务B 有了注册中心之后&a…...
CSS语言的硬件驱动
CSS语言的硬件驱动探讨 引言 随着信息技术的迅猛发展,硬件和软件之间的交互愈发复杂,特别是在嵌入式系统、物联网设备等领域,硬件驱动程序的开发变得至关重要。而在众多编程语言中,CSS(层叠样式表)作为一…...
浅入浅出:从传统开发者角度去了解区块链和智能合约之间的关系
前言 在传统开发者视角:智能合约与区块链数据库探秘文中我为大家简单的讲解了DApp开发中智能合约开发和传统开发中数据存储层面的不同。而智能合约则是DApp中重要的组成部分,如同传统开发中的后端。 但是我们不要忘记的是:智能合约是应区块链而生的。 那么对于区块链来说…...
使用人工智能大模型DeepSeek,如何免费辅助教学?
今天我们学习DeepSeek工具如何辅助教学?DeepSeek功能很强大,带动人工智能快速发展,这里给DeepSeek点个赞。免费手把手学习视频地址:https://edu.csdn.net/learn/40402/666415 第一步,进入DeepSeek官网。打开google浏览器&#x…...
leetcode-代码随想录-链表-链表理论基础
链表: 通过指针串联在一起的线性结构;每个节点包含两部分:数据域、指针域(存放下一个节点的指针)入口节点:称为 头节点 head最后一个节点的指针指向 NULL(空指针) 链表的类型 1. 单…...
dify中配置使用Ktransformer模型
一共是两个框架一个是Ktransformer,一个是dify。 Ktransformer用来部署LLM,比如Deepseek,而LLm的应用框架平台Dify主要用来快速搭建基于LLM应用。 这篇教程主要是用来介绍两个框架的交互与对接的,不是部署Ktransformer也部署部署Dify,要部署Dify、Ktransformer可以直接参考…...
解释区块链技术的应用场景和优势
区块链技术是一种基于分布式账本的技术,被广泛应用于多个领域。以下是区块链技术的主要应用场景和优势: 应用场景: 金融领域:区块链可以用于支付结算、跨境汇款、智能合约等金融服务,提高交易效率和降低成本。物联网…...
明清两朝全方位对比
明清两朝是中国历史上最后两个封建王朝,在政治、经济、文化等方面存在显著差异,以下为主要区别: 一、政治制度 皇权集中程度 明朝:废除丞相制度,设内阁辅助皇帝,但中后期宦官专权(如刘瑾、魏…...
Mysql的事务
事务的概念 简单的说事务就是一个连贯性任务,只有一起成功或者一起失败的说法。在mysql的事务中要么事务里的sql语句成功执行,其中有出错就回滚到事务开始时候的状态。对于已经提交的事务来说,该事务对数据库所做的修改将永久生效事务的四大特性ACID 原子性(Atomicity):一件…...
chromium魔改——绕过无限debugger反调试
在进行以下操作之前,请确保已完成之前文章中提到的 源码拉取及编译 部分。 如果已顺利完成相关配置,即可继续执行后续操作。 在浏览器中实现“无限 debugger”的反调试技术是一种常见的手段,用于防止他人通过开发者工具对网页进行调试或逆向…...
【力扣hot100题】(051)腐烂的橘子
我讨厌图论。 这道题写了特别久,不过好歹也是写出来了…… 方法是先将橘子全部遍历一遍,做两件事:①找出所有连通的橘子②找出所有腐烂的橘子,设置一个vector<queue<int>>,每个vector元素代表一片连通的…...
PyTorch实现线性回归的基础写法与封装API写法
目录 1. 基础写法 1.1导包 2.2加载读取数据 2.3原始数据可视化(画图显示) 2.4线性回归的(基础)分解写法 2.5定义训练过程 2.PyTorch实现 线性回归的封装写法(实际项目中的常用写法) 2.1创建线性回归模型 2.2定义损失函数 2.3定义优化器 2.4定义训练过程 1…...
【蓝桥杯】算法笔记3
1. 最长上升子序列(LIS) 1.1. 题目 想象你有一排数字,比如:3, 1, 2, 1, 8, 5, 6 你要从中挑出一些数字,这些数字要满足两个条件: 你挑的数字的顺序要和原来序列中的顺序一致(不能打乱顺序) 你挑的数字要一个比一个大(严格递增) 问:最多能挑出多少个这样的数字? …...
【Linux】条件变量封装类及环形队列的实现
📢博客主页:https://blog.csdn.net/2301_779549673 📢博客仓库:https://gitee.com/JohnKingW/linux_test/tree/master/lesson 📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正! &…...
wsl2 配置ubuntu 固定ip
提示:环境搭建 文章目录 前言一、安装sshd 服务1. ubuntu 子系统安装 openssh-server2.配置sshd 开启密码链接3.配置sshd 服务开机启动 二、配置固定IP1 查看i2 查看路由3 查看wsl虚拟网卡4 配置wsl 子系统网卡4 设置生效 三、问题1. ssh 无法远程 前言 提示&#…...
电机控制学习路线
一、基础理论准备阶段 电路与电子技术 电路分析基础(基尔霍夫定律、动态电路分析) 模拟电子技术(放大器、滤波电路、功率器件) 数字电子技术(逻辑电路、微控制器基础) 数学工具 线性代数(矩…...
Sensodrive力控关节模组SensoJoint:TÜV安全认证助力机器人开发
在机器人技术领域,安全性和开发效率是行业关注的重点。SensoDrive的SensoJoint 机器人力控关节模组,凭借其可靠的安全性能和高效的开发优势,正在为机器人开发提供有力支持。 2025年3月31日,SensoDrive的 SensoJoint 力控关节模组获…...
【橘子大模型】Runnable和Chain以及串行和并行
一、Runnable 前面我们实现了一些关于如何和大模型进行交互的操作。那么我们此时来回顾一下我们当前进行的结构。 我们已经很清楚这些操作的具体含义了,所以我这里就不在多介绍了。我们来看其中的几个点 1、用户那边就是客户,没啥说的。 2、langchain&…...
数据结构 -- 图的存储
图的存储 邻接矩阵法 邻接矩阵存储不带权图 0 - 表示两个顶点不邻接 1 - 表示两个顶点邻接 在无向图中,每条边在矩阵中对应两个1 在有向图中,每条边在矩阵中对应一个1 //不带权图的邻接矩阵存储 #define MaxVertexNum 100 //顶点数目的最大值 typed…...
基于大模型预测不稳定性心绞痛的多维度研究与应用
目录 一、引言 1.1 研究背景与意义 1.2 研究目的 1.3 国内外研究现状 二、不稳定性心绞痛概述 2.1 定义与分类 2.2 发病机制 2.3 临床表现 三、大模型技术原理与应用基础 3.1 大模型介绍 3.2 在医疗领域的应用现状 3.3 用于不稳定性心绞痛预测的可行性 四、术前预…...
【Java集合】LinkedList源码深度分析
参考笔记:java LinkedList 源码分析(通俗易懂)_linkedlist源码分析-CSDN博客 目录 1.前言 2.LinkedList简介 3.LinkedList的底层实现 4.LinkedList 与 ArrayList 的对比 4.1 如何选择 4.2 对比图 5.LinkedList 源码Debug 5.1 add(E e) ÿ…...
Java 大视界 -- Java 大数据在智能供应链库存优化与成本控制中的应用策略(172)
💖亲爱的朋友们,热烈欢迎来到 青云交的博客!能与诸位在此相逢,我倍感荣幸。在这飞速更迭的时代,我们都渴望一方心灵净土,而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识,也…...
高并发系统架构设计核心要点的结构化提炼【大模型总结】
以下是对高并发系统架构设计核心要点的结构化提炼,结合技术深度与实践视角,以清晰的层次呈现关键策略与实现路径: 一、核心理念重塑 1. 容错优先思维 设计哲学:从"零故障"转向"可恢复性"设计,接…...
【C#深度学习之路】如何使用C#实现Stable Diffusion的文生图功能
【C#深度学习之路】如何使用C#实现Stable Diffusion的文生图功能 项目背景项目实现写在最后项目下载链接 本文为原创文章,若需要转载,请注明出处。 原文地址:https://blog.csdn.net/qq_30270773/article/details/147002073 项目对应的Github地…...
k8s的pod的概述和配置
概念 Pod 容器组 是一个k8s中一个抽象的概念,用于存放一组 container(可包含一个或多个 container 容器,即图上正方体),以及这些 container (容器)的一些共享资源。这些资源包括: 共享存储&…...
RTOS任务句柄的作用
任务句柄(Task Handle)在 FreeRTOS 中的作用详解 任务句柄(TaskHandle_t)是 FreeRTOS 中用于 唯一标识和管理任务 的核心机制,本质是一个指向任务控制块(TCB)的指针。说明即便创建的任务名相同,但对应的任务句柄一定是不同。 它在任务管理、通信、调试中起到关键作用,…...
OpenVLA-OFT——微调VLA的三大关键设计:并行解码、动作分块、连续动作表示以及L1回归目标
前言 25年3.26日,这是一个值得纪念的日子,这一天,我司「七月在线」的定位正式升级为了:具身智能的场景落地与定制开发商 ,后续则从定制开发 逐步过渡到 标准产品化 比如25年q2起,在定制开发之外࿰…...
LocaDate、LocalTime、LocalDateTime
Java8的时间处理 Java的时间处理在早期版本中存在诸多问题(如 java.util.Date 和 java.util.Calendar 的混乱设计),但Java8引入了引入了全新的 java.time包(基于JSR 310),提供了更清晰、线程安全且强大的时…...
哈密尔顿路径(Hamiltonian Path)及相关算法题目
哈密尔顿路径要求访问图中每个顶点恰好一次,通常用于解决旅行商问题(TSP)或状态压缩DP问题。 哈密尔顿路径(Hamiltonian Path)是指在一个图中经过每个顶点恰好一次的路径。如果这条路径的起点和终点相同(即…...