本文最后更新于:2024年10月7日 下午
一、软件设计模式基础概述
软件设计模式简介
软件设计模式类型
软件设计模式原则
开闭原则(Open Close
Principle)
- 对扩展开放,对修改关闭
- 再程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。
- 想要达到这样的效果,需要使用接口和抽象类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| public class FruitShop { public void sellFruit(Fruit fruit) { fruit.sell(); } }
public abstract class Fruit { int fruit_type;
public abstract void sell(); }
public class Apple extends Fruit { Apple() { super.fruit_type = 1; }
@Override public void sell() { System.out.println("卖出一斤苹果!"); } }
public class Banana extends Fruit { Banana() { super.fruit_type = 2; }
@Override public void sell() { System.out.println("卖出一斤香蕉!"); } }
public class Watermelon extends Fruit { Watermelon() { super.fruit_type = 3; }
@Override public void sell() { System.out.println("卖出一斤西瓜!"); } }
|
里氏代换原则(Liskov
Substitution Principle)
例子:鸟不一定都会飞,所以鸟的基类里不要设置飞的方法,要设置跑的方法。
依赖倒转原则(Dependence
Inversion Principle)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| public class DependenceInversionPrinciple {
public static void main(String[] args) { Person person = new Person(); person.receive(new Email()); person.receive(new WeChat()); } }
interface IReceiver { public String getInfo(); }
class Email implements IReceiver { @Override public String getInfo() { return "电子邮件信息:hello,Email"; } }
class WeChat implements IReceiver { @Override public String getInfo() { return "微信消息:hello,WeChat"; } }
class Person { public void receive(IReceiver receiver) { System.out.println(receiver.getInfo()); } }
|
接口隔离原则(Interface
Segregation Principle)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| public class InterfaceSegregationPrinciple {
public interface SwimAnimalAction { void swim(); }
public interface FlyAnimalAction { void fly(); }
public interface EatAnimalAction { void eat(); }
public class Dog implements EatAnimalAction, SwimAnimalAction { @Override public void eat() { System.out.println("Dog eat"); }
@Override public void swim() { System.out.println("Dog swim"); } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| public class UnSegregation { public interface AnimalAction { void eat();
void sleep();
void fly();
void swim(); }
public class Dog implements AnimalAction { @Override public void eat() { System.out.println("Dog eat"); }
@Override public void sleep() { System.out.println("Dog sleep"); }
@Override public void fly() { System.out.println("Dog can't fly"); }
@Override public void swim() {
} } }
|
迪米特法则(Demeter
Principle)(又称最少知道原则)
- 一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
- 降低系统的耦合度,使类与类之间保持松耦合状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| public class DemeterPrinciple { public class Computer { private void saveCurrentTask() { }
private void closeService() { }
private void closeScreen() { }
private void closePower() { }
public void close() { saveCurrentTask(); closeService(); closeScreen(); closePower(); } }
public class User { private Computer computer;
public void clickCloseButton() { computer.close(); } } }
|
合成复用原则(Composite
Reuse Principle)
- 复用类我们可以通过继承和合成两种方式来实现。
- 尽量使用合成/聚合的方式,而不是使用继承。
- 继承的优点:容易实现并且修改和扩展继承来的内容。
- 继承的缺点:它最大的缺点就是增加了类之间的依赖,继承是属于白箱复用,父类对子类来说是透明的,这破坏了类的封装性。
- 合成复用存在的缺点就是在系统中会存在较多的对象需要管理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class CompositeReusePrinciple { public static void main(String[] args) { B b = new B(); b.methodB(new A()); } }
class A { public void methodA() { System.out.println("A类的方法执行了"); } }
class B { public void methodB(A a) { System.out.println("B类的方法执行了"); a.methodA(); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| public class Test2 { public static void main(String[] args) { B b = new B(); System.out.println("使用聚合的执行结果:"); b.setA(new A()); b.methodB(); } }
class A { public void methodA() { System.out.println("A类的方法执行了"); } }
class B { private A a;
public A getA() { return a; }
public void setA(A a) { this.a = a; }
public void methodB() { System.out.println("B类的方法执行了"); this.a.methodA(); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class Test3 { public static void main(String[] args) { B b = new B(); System.out.println("使用组合的执行结果:"); b.methodB(); } }
class A { public void methodA() { System.out.println("A类的方法执行了"); } }
class B { private A a = new A();
public void methodB() { System.out.println("B类的方法执行了"); this.a.methodA(); } }
|
- 聚合与组合的区别:
- 关联(Association):指一个类的实例A使用另外一个类的实例B,这两个对象之间为关联关系,关联关系分为单项关联和双向关联。
- 聚合(Aggregation)关系是关联关系的一种,耦合度强于关联,他们的代码表现是相同的,仅仅是在语义上有所区别:关联关系的对象间是相互独立的,而聚合关系的对象之间存在着包容关系,他们之间是“整体-个体”的相互关系。
- 相比于聚合,组合(Compostion)是一种耦合度更强的关联关系。存在组合关系的类表示“整体-部分”的关联关系,“整体”负责“部分”的生命周期,他们之间是共生共死的;并且“部分”单独存在时没有任何意义。
二. 常用软件设计模式
创建型模式
单例模式(Singleton)
单例模式提供了一种创建对象的最佳方式。
单例模式涉及到单一的类:
- 该类负责创建自己的对象,并确保只有单个对象被创建。
- 该类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
To be noted:
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
- 模式意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
- 解决问题:一个全局使用的类频繁地创建与销毁。
- 使用时机:当你想控制实例数目,节省系统资源的时候。
- 解决方案:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
- 关键代码:构造函数是私有的。
- 应用实例:
- 一个班级只有一个班主任。
- 一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
1.
懒汉式,线程不安全(最基础的实现方式)
- Lazy初始化
- 无多线程安全,在多线程不能正常工作
- 没有加锁
synchronized
,严格意义上它并不算单例模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class LazySingleton { private static LazySingleton instance;
private LazySingleton() {
}
public static LazySingleton getInstance() { if (instance == null) { instance = new LazySingleton(); } return instance; }; }
|
2. 懒汉式,线程安全
- lazy初始化:第一次调用才初始化,避免内存浪费。
- 多线程安全,能够在多线程中很好的工作,但效率很低,99%的情况下不需要同步。
- 缺点:必须加锁
synchronized
才能保证单例,但加锁会影响效率
1 2 3 4 5 6 7 8 9 10 11
| public class HungrySingleton { private static HungrySingleton instance = new HungrySingleton();
private HungrySingleton() {
}
public static HungrySingleton getInstance() { return instance; } }
|
3. 饿汉式
- 无Lazy初始化,比较常用,类加载时就初始化,浪费内存,容易产生垃圾对象。
- 多线程安全,没有加锁,执行效率会提高。
- 基于
classloader
机制避免了多线程的同步问题,不过,instance
在类加载时就实例化。
1 2 3 4 5 6 7 8 9 10 11 12
| public class HungrySingleton { private static HungrySingleton instance = new HungrySingleton();
private HungrySingleton() {
}
public static HungrySingleton getInstance() { return instance; } }
|
4.
双检锁/双重校验锁(DCL,即double-checked locking)
- JDK版本:JDK1.5起
- Lazy初始化
- 采用双锁机制,安全且在多线程下保持高性能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class DCLSingleton { private volatile static DCLSingleton instance;
private DCLSingleton() { }
public static DCLSingleton getInstance() { if (instance == null) { synchronized (DCLSingleton.class) { if (instance == null) { instance = new DCLSingleton(); } } } return instance; } }
|
5. 登记式/静态内部类
- Lazy初始化:利用了
classloader
机制来保证初始化instance
时只有一个线程,而Singleton
类被装载了,instance
不一定被初始化。
SingletonHolder
类没有被主动使用,只有通过显式调用getInstance
方法时,才会显式装载SingletonHolder
类,从而实例化instance
。实现Singleton
类延迟加载。
- 多线程安全,达到双检锁的功效
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); }
private Singleton() {
}
public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } }
|
final
关键字的作用
- 修饰类:当一个类被声明为
final
时,它不能被继承。例如,final class Singleton {}
,这个类不能被其他类继承。
- 修饰方法:当一个方法被声明为
final
时,它不能被子类重写。例如,public final void method() {}
,这个方法不能在子类中被重写。
- 修饰变量:当一个变量被声明为
final
时,它的值不能被改变。对于基本数据类型,final
变量是常量,一旦被初始化,就不能再被修改。对于引用类型,final
变量引用的对象不能被改变,但对象本身的内容可以改变。
总结
- 优点
- 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。
- 避免对资源的多重占用(比如写文件操作)。
- 缺点
- 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
- 使用场景:
- 要求生产唯一序列号
- WEB中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
- 创建的一个对象需要消耗的资源过多,比如I/O与数据库的连接。
- 注意事项:
getInstance
方法需要使用同步锁synchronized
(Singleton.class
)防止多线程同时进入造成instance
被多次实例化。
工厂模式(Factory)
- 使用场景:
- 日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。
- 数据库访问,数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。
- 设计一个连接服务器的框架,需要三个协议,
POP3
,IMAP
,HTTP
,可以把这三个作为产品类,共同实现一个接口。
- 注意事项:
- 作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。
- 需要注意的地方就是复杂对象适合使用工程模式
- 而简单对象,特别是只需要通过
new
就可以完成对象的创建的对象,无需使用工厂模式。
- 如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度
抽象工厂模式
- 抽象工厂模式是围绕一个超级工厂创建其他工厂。
- 该超级工厂又称为其他工厂的工厂,提供了一种创建对象的最佳方式。
- 在抽象工厂模式中,接口时负责创建一个相关对象的工厂,不需要显式指定他们的类。每个生产的工厂,都能按照工厂模式提供对象。
- 模式意图:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
- 解决问题:主要解决接口选择的问题。
- 使用时机:系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。
- 解决方案:在一个产品族里面,定义多个产品。
- 关键代码:在一个工厂里聚合多个同类产品。
示例代码
- 创建
Shape
和Color
接口和实现这些接口的实体类。
1 2 3 4 5 6
| public interface Shape { void draw(); } public interface Color { void fill(); }
|
1 2 3 4 5 6 7 8 9 10 11 12
| public class Rectangle implements Shape { @Override public void draw() { System.out.println("Inside Rectangle::draw() method."); } } public class Red implements Color { @Override public void fill() { System.out.println("Inside Red::fill() method."); } }
|
1 2 3 4 5 6 7
| public abstract class AbstractFactory {
public abstract Color getColor(String color);
public abstract Shape getShape(String shape); }
|
- 定义工厂类
ShapeFactory
和ColorFactory
,均扩展了AbstractFactory
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class ShapeFactory extends AbstractFactory { @Override public Shape getShape(String shapeType) { if (shapeType == null) { return null; } if (shapeType.equalsIgnoreCase("CIRCLE")) { return new Circle(); } else if (shapeType.equalsIgnoreCase("RECTANGLE")) { return new Rectangle(); } else if (shapeType.equalsIgnoreCase("SQUARE")) { return new Square(); } return null; }
@Override public Color getColor(String color) { return null; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class ColorFactory extends AbstractFactory {
@Override public Shape getShape(String shapeType) { return null; }
@Override public Color getColor(String color) { if (color == null) { return null; } if (color.equalsIgnoreCase("RED")) { return new Red(); } else if (color.equalsIgnoreCase("GREEN")) { return new Green(); } else if (color.equalsIgnoreCase("BLUE")) { return new Blue(); } return null; } }
|
- 创建一个工厂创造器/生成器类
FactoryProducer
。
1 2 3 4 5 6 7 8 9 10
| public class FactoryProducer { public static AbstractFactory getFactory(String choice) { if (choice.equalsIgnoreCase("SHAPE")) { return new ShapeFactory(); } else if (choice.equalsIgnoreCase("COLOR")) { return new ColorFactory(); } return null; } }
|
AbstractFactoryPatternDemo
类使用FactoryProducer
来获取AbstractFactory
对象。它将向AbstractFactory
传递形状信息Shape
(CIRCLE
/RECTANGLE
/SQUARE
),以获取它所需要对象的类型。
- 同时他还向
AbstractFactory
传递颜色信息Color
(RED
/GREEN
/BLUE
),以便获取它所需对象的类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| public class AbstractFactoryPatternDemo { public static void main(String[] args) { AbstractFactory shapeFactory = FactoryProducer.getFactory("SHAPE");
Shape shape1 = shapeFactory.getShape("CIRCLE");
shape1.draw();
Shape shape2 = shapeFactory.getShape("RECTANGLE");
shape2.draw();
Shape shape3 = shapeFactory.getShape("SQUARE");
shape3.draw();
AbstractFactory colorFactory = FactoryProducer.getFactory("COLOR");
Color color1 = colorFactory.getColor("RED");
color1.fill();
Color color2 = colorFactory.getColor("GREEN");
color2.fill();
Color color3 = colorFactory.getColor("BLUE");
color3.fill(); } }
|
总结
- 优点
- 当一个产品族的多个对象被设计成一起工作时,他能保证客户端始终只使用同一个产品族中的对象。
- 缺点
- 产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的
Creator
里加代码,又要在具体的里面加代码。
结构性模式
适配器模式(Adapter)
- 适配器模式(Adapter
Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。
- 适配器模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。
- 例如:读卡器是作为内存卡和笔记本之间的适配器
- 模式意图:将一个类的接口转换成客户希望的另一个接口。使原本由于接口不兼容而不能一起工作的那些类可以一起工作。
- 解决问题:解决在软件系统中,常常要将一些“现存的对象”放到新的环境中,而新环境要求的接口是现对象不能满足的。
- 使用时机:
- 系统需要使用现有的类,而此类的接口不符合系统的需要。
- 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。
- 通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口)
- 解决方案:继承或依赖(推荐)
- 关键代码:适配器继承或依赖已有的对象,实现想要的目标接口。
- 应用实例:
- 美国电器110V,中国220V,就要有一个适配器将110V转化为220V。
- JAVA JDK
1.1提供了
Enumeration
接口,而在1.2中提供了Iterator
接口,想要使用1.2的JDK,则要将以前系统的Enumeration
接口转化为Iterator
接口,这是就需要适配器模式。
- JAVA中的jdbc(Java DataBase Connectivity)。
示例代码
MediaPlayer
接口和一个实现了MediaPlayer接口的实体类AudioPlayer
。默认情况下,AudioPlayer
可播放mp3
格式的文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public interface MediaPlayer { public void play(String audioType, String fileName); }
public class AudioPlayer implements MediaPlayer { @Override public void play(String audioType, String fileName) { if (audioType.equalsIgnoreCase("mp3")) { System.out.println("Playing mp3 file. Name: " + fileName); } else if (audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")) { MediaAdapter mediaAdapter = new MediaAdapter(audioType); mediaAdapter.play(audioType, fileName); } else { System.out.println("Invalid media. " + audioType + " format not supported"); } } }
|
- 另一个接口
AdvancedMediaPlayer
和实现了AdvancedMediaPlayer
接口的实体类。该类可以播放vlc
和mp4
文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public interface AdvancedMediaPlayer { public void playVlc(String fileName);
public void playMp4(String fileName); }
public class VlcPlayer implements AdvancedMediaPlayer { @Override public void playVlc(String fileName) { System.out.println("Playing vlc file. Name: " + fileName); }
@Override public void playMp4(String fileName) { } }
public class Mp4Player implements AdvancedMediaPlayer { @Override public void playVlc(String fileName) { }
@Override public void playMp4(String fileName) { System.out.println("Playing mp4 file. Name: " + fileName); } }
|
- 如果要让
AudioPlayer
播放其他格式的音频文件。为了实现这个功能,我们需要创建一个实现了MediaPlayer
接口的适配类MediaAdapter
,并使用AdvancedMediaPlayer
对象来播放所需格式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class MediaAdapter implements MediaPlayer { AdvancedMediaPlayer advancedMediaPlayer; public MediaAdapter(String audioType) { if (audioType.equalsIgnoreCase("vlc")) { advancedMediaPlayer = new VlcPlayer(); } else if (audioType.equalsIgnoreCase("mp4")) { advancedMediaPlayer = new Mp4Player(); } }
@Override public void play(String audioType, String fileName) { if (audioType.equalsIgnoreCase("vlc")) { advancedMediaPlayer.playVlc(fileName); } else if (audioType.equalsIgnoreCase("mp4")) { advancedMediaPlayer.playMp4(fileName); } } }
|
AudioPlayer
使用适配类MediaPlayer
传递所需的音频类型,不需要知道能播放所需格式音频的实际类。AdapterPatternDemo
类使用AudioPlayer
类来播放各种格式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class AudioPlayer implements MediaPlayer { @Override public void play(String audioType, String fileName) { if (audioType.equalsIgnoreCase("mp3")) { System.out.println("Playing mp3 file. Name: " + fileName); } else if (audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")) { MediaAdapter mediaAdapter = new MediaAdapter(audioType); mediaAdapter.play(audioType, fileName); } else { System.out.println("Invalid media. " + audioType + " format not supported"); } } }
|
1 2 3 4 5 6 7 8 9 10
| public class AdapterPatternDemo { public static void main(String[] args) { AudioPlayer audioPlayer = new AudioPlayer();
audioPlayer.play("mp3", "beyond the horizon.mp3"); audioPlayer.play("mp4", "alone.mp4"); audioPlayer.play("vlc", "far far away.vlc"); audioPlayer.play("avi", "mind me.avi"); } }
|
类适配器模式(继承或实现关系)
- 目标(
Target
)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
1 2 3
| public interface Target { public void request(); }
|
- 适配者(
Adaptee
)类:它是被访问和适配的现存组件库中的组件接口。
1 2 3 4 5
| public class Adaptee { public void specificRequest() { System.out.println("适配者中的业务代码被调用!"); } }
|
- 适配器(
Adapter
)类:它是一个转换器,通过继承或引用适配者的对象,把适配者转换成目标接口,让客户按目标接口的格式访问适配者。
1 2 3 4 5 6 7
| public class ClassAdapter extends Adaptee implements Target { @Override public void request() { specificRequest(); } }
|
1 2 3 4 5 6 7 8 9
| public class ClassAdapterTest { public static void main(String[] args) { Target target = new ClassAdapter(); target.request(); } }
|
双向适配器模式
- 适配器模式(
Adapter
)可扩展为双向适配器模式(Two Way Adapter
)
- 双向适配器类可实现适配者(
Adaptee
)接口和目标(Target
)接口双向转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public interface TwoWayTarget { public void request(); } public class TargetRealize implements TwoWayTarget { @Override public void request() { System.out.println("目标代码被调用!"); } }
public interface TwoWayAdaptee { public void specificRequest(); } public class AdapteeRealize implements TwoWayAdaptee { @Override public void specificRequest() { System.out.println("适配者代码被调用!"); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class TwoWayAdapter implements TwoWayTarget, TwoWayAdaptee { private TwoWayTarget target; private TwoWayAdaptee adaptee;
public TwoWayAdapter(TwoWayTarget target) { this.target = target; }
public TwoWayAdapter(TwoWayAdaptee adaptee) { this.adaptee = adaptee; }
public void request() { adaptee.specificRequest(); }
public void specificRequest() { target.request(); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class TwoWayAdapterTest { public static void main(String[] args) { System.out.println("Target通过双向适配器访问Adaptee:"); TwoWayAdaptee adaptee = new AdapteeRealize(); TwoWayTarget target = new TwoWayAdapter(adaptee); target.request(); System.out.println("------------------------------------------"); System.out.println("Adaptee通过双向适配器访问Target:"); target = new TargetRealize(); adaptee = new TwoWayAdapter(target); adaptee.specificRequest();
} }
|
总结
- 优点
- 可以让任何两个没有关联的类一起运行。
- 提高了类的复用
- 增加了类的透明度
- 灵活性好
- 缺点
- 过多使用适配器,会让系统非常凌乱,不易整体把握
- 比如,明明看到调用A接口,其实内部被是配成了B接口的实现
- 一个系统如果出现太多这种情况,无异于一场灾难
- 如果非必要,可以不使用适配器,而是直接对系统进行重构
- 使用场景:
- 有动机地修改一个正常运行的系统的接口,这时应该考虑适配器模式
- 注意事项:
- 适配器不是在详细设计时添加的,而是解决正在服役的项目的问题!
桥接模式(Bridge)
- 桥接模式(Bridge
Pattern)通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。
- 桥接模式涉及到一个作为桥接的接口,使得实体类的功能独立于接口实现类。这两种类型可被结构化改变而互不影响。
- 模式意图:将抽象部分与实现部分分离,使他们都可以独立的变化。
- 解决问题:在有多种可能会变化的情况下,用继承会造成类爆炸问题,扩展起来不灵活。
- 使用时机:实现系统可能有多个角度分类,每一种角度都可能变化。
- 解决方案:把这种多角度分类分离出来,让它们独立变化,减少他们的耦合。
- 关键代码:抽象类依赖实现类。
示例代码
- 实现化(
Implementor
)角色:定义实现化角色的接口,供扩展抽象化角色调用。
- 具体实现化(
Concrete Implementor
)角色:给出实现化角色接口的具体实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public interface Color { String getColor(); }
public class Yellow implements Color { @Override public String getColor() { return "yellow"; } }
public class Red implements Color { @Override public String getColor() { return "red"; } }
|
- 抽象化(
Abstraction
)角色:定义抽象类,并包含一个对实现化对象的引用。
- 扩展抽象化(
Refined Abstraction
)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public abstract class Bag { protected Color color;
public void setColor(Color color) { this.color = color; }
public abstract String getName(); }
public class HandBag extends Bag { @Override public String getName() { return color.getColor() + "HandBag"; } }
public class Wallet extends Bag { @Override public String getName() { return color.getColor() + "Wallet"; } }
|
1 2 3 4 5 6 7
| public class BagManage { public static void main(String[] args) { Bag bag = new HandBag(); bag.setColor(new Red()); System.out.println(bag.getName()); } }
|
总结
- 优点
- 抽象和实现的分离。
- 优秀的扩展能力。
- 实现细节对用户透明。
- 缺点
- 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
- 使用场景:
- 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
- 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。
- 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
- 注意事项:
- 对于两个独立变化的维度,使用桥接模式再适合不过了
- 在软件开发中,有时桥接(Bridge)模式可与适配器模式联合使用。
- 当桥接(Bridge)模式的实现化角色的接口与现有类的接口不一致时,可以在二者中间定义一个适配器将二者连接起来
代理模式(Proxy)
- 代理模式(Proxy
Pattern):由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
- 代理模式也叫做委托模式。
- 模式意图:为其他对象提供一种代理以控制对这个对象的访问。
- 解决问题:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
- 使用时机:想在访问一个类时做一些控制。
- 解决方案:增加中间层。
- 关键代码:实现与被代理类组合。
静态代理
- 创建一个
Image
接口和实现了Image
接口的实体类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public interface Image { void display(); }
public class RealImage implements Image { private String filename;
public RealImage(String filename) { this.filename = filename; loadFromDisk(filename); }
@Override public void display() { System.out.println("Displaying " + filename); }
private void loadFromDisk(String filename) { System.out.println("Loading " + filename); } }
|
ProxyImage
是一个代理类,减少RealImage
对象加载的内存占用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class ProxyImage implements Image { private RealImage realImage; private String filename;
public ProxyImage(String filename) { this.filename = filename; }
@Override public void display() { if (realImage == null) { realImage = new RealImage(filename); } realImage.display(); } }
|
ProxyPatternDemo
类使用ProxyImage
来获取要加载的Image
对象,并按照需求进行显示。
1 2 3 4 5 6 7 8 9
| public class ProxyPattrenDemo { public static void main(String[] args) { Image image = new ProxyImage("huge_image.jpg"); image.display(); image.display(); } }
|
- 优点
- 可以做到在不修改目标对象功能前提下,对目标功能扩展。
- 缺点
- 因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多。
- 一旦接口增加方法,目标对象与代理对象都要维护。
JDK动态代理
- JDK自带动态代理。
- 其通过自己实现
InvocationHandler
来实现动态代理,真正的代理对象由JDK在运行时为我们动态的来创建。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| interface Car { void run(); }
class Benz implements Car { @Override public void run() { System.out.println("奔驰车跑起来了"); } }
class CarUtils { public static void methodBefore() { System.out.println("跑之前要点火。。。。。。"); }
public static void methodAfter() { System.out.println("跑起来后会换挡。。。。。。"); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy;
class MyInvocationHandler implements InvocationHandler { private Object target;
public void setTarget(Object target) { this.target = target; }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { CarUtils.methodBefore(); method.invoke(target, args); CarUtils.methodAfter(); return null; } }
class MyProxyFactory { public static Object getProxy(Object target) { MyInvocationHandler handle = new MyInvocationHandler(); handle.setTarget(target); Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handle); return proxy; } }
public class ProxyTest { public static void main(String[] args) { Car car = new Benz(); Car carProxy = (Car) MyProxyFactory.getProxy(car); carProxy.run(); } }
|
JDK动态代理:
- 虽然相对于静态代理,动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度。
- 但是JDK自带动态代理只能支持实现了Interface的类。
总结
- 优点
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
- 代理对象可以扩展目标对象的功能;例如我们想给项目加入缓存、
日志这些功能,
我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
- 缺点
- 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
- 增加了系统的复杂度;
行为型模式
- 中介者模式(Mediator
Pattern):定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。中介者模式又叫调停模式,它是迪米特法则(最少知道)的典型应用。
- 中介者模式是用来降低多个对象和类之间的通信复杂性。这种模式提供了一个中介类,该类通常处理不同类之间的通信,并支持松耦合,使代码易于维护。
- 模式意图:用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
- 解决问题:对象与对象之间存在大量的关联关系,这样势必会导致系统的结构变得很复杂,同时若一个对象发生改变,我们也需要跟踪与之相关联的对象,同时做出相应的处理。
- 使用时机:多个类相互耦合,形成了网状结构。
- 解决方案:将网状结构分离为星型结构。
- 关键代码:对象
Colleague
之间的通信封装到一个类中单独处理。
示例代码
- 抽象中介者(
Mediator
)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。
- 具体中介者(
Concrete Mediator
)角色:实现中介者接口,定义一个List
来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public interface Mediator { public abstract void register(Colleague colleague);
public abstract void relay(Colleague c1); }
class ConcreteMediator implements Mediator { private List<Colleague> colleagues = new ArrayList<Colleague>();
public void register(Colleague colleague) { if (!colleagues.contains(colleague)) { colleagues.add(colleague); colleague.setMediator(this); } }
public void relay(Colleague c1) { for (Colleague ob : colleagues) { if (!ob.equals(c1)) { ((Colleague) ob).receive(); } } } }
|
- 抽象同事类(
Colleague
)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
- 具体同事类(
Concrete Colleague
)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| public abstract class Colleague { protected Mediator mediator;
public void setMediator(Mediator mediator) { this.mediator = mediator; }
public abstract void receive();
public abstract void send(); }
class ConcreteColleague1 extends Colleague { @Override public void receive() { System.out.println("具体同事类1收到请求。"); }
@Override public void send() { System.out.println("具体同事类1发出请求。"); mediator.relay(this); } }
class ConcreteColleague2 extends Colleague { @Override public void receive() { System.out.println("具体同事类2收到请求。"); }
@Override public void send() { System.out.println("具体同事类2发出请求。"); mediator.relay(this); } }
|
1 2 3 4 5 6 7 8 9 10 11 12
| public class MediatorPattern { public static void main(String[] args) { Mediator md = new ConcreteMediator(); Colleague c1 = new ConcreteColleague1(); Colleague c2 = new ConcreteColleague2(); md.register(c1); md.register(c2); c1.send(); System.out.println("-------------"); c2.send(); } }
|
中介+单例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class SimpleMediator { private static SimpleMediator smd = new SimpleMediator(); private List<SimpleColleague> colleagues = new ArrayList<SimpleColleague>();
private SimpleMediator() { }
public static SimpleMediator getMediator() { return smd; }
public void register(SimpleColleague colleague) { colleagues.add(colleague); }
public void relay(SimpleColleague c1) { for (SimpleColleague ob : colleagues) { if (!ob.equals(c1)) { ob.receive(); } } } }
|
- 同事对象不持有中介者,而是在需要的时候直接获取中介者对象并调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| interface SimpleColleague { void receive();
void send(); }
class SimpleConcreteColleague1 implements SimpleColleague { SimpleConcreteColleague1() { SimpleMediator smd = SimpleMediator.getMediator(); smd.register(this); }
@Override public void receive() { System.out.println("具体同事类1收到请求"); }
@Override public void send() { System.out.println("具体同事类1发出请求"); SimpleMediator smd = SimpleMediator.getMediator(); smd.relay(this); } }
class SimpleConcreteColleague2 implements SimpleColleague { SimpleConcreteColleague2() { SimpleMediator smd = SimpleMediator.getMediator(); smd.register(this); }
@Override public void receive() { System.out.println("具体同事类2收到请求"); }
@Override public void send() { System.out.println("具体同事类2发出请求"); SimpleMediator smd = SimpleMediator.getMediator(); smd.relay(this); } }
|
总结
- 优点
- 降低了类的复杂度,将一对多转化成了一对一。
- 各个类之间的解耦。
- 符合迪米特最小知道原则。
- 使用场景:
- 系统中对象之间存在比较复杂的引用关系,导致它们之间的依赖关系结构混乱而且难以复用该对象。
- 想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。
观察者模式(Observer)
- 又被称为发布-订阅/模型-视图模式,属于行为型设计模式的一种。
- 是一个在项目中经常使用的模式。指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。
- 模式意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。
- 解决问题:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
- 使用时机:
一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。
- 解决方案:使用面向对象技术,可以将这种依赖关系弱化。
- 关键代码:在抽象类里有一个
ArrayList
存放观察者们。
示例代码
- 抽象目标(
Subject
)角色:提供一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
- 具体目标(
Concrete Subject
)角色:实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| abstract class Subject { protected List<Observer> observers = new ArrayList<>();
public void add(Observer observer) { observers.add(observer); }
public void remove(Observer observer) { observers.remove(observer); }
public abstract void notifyObserver(); }
class ConcreteSubject extends Subject { @Override public void notifyObserver() { System.out.println("具体目标发生改变..."); System.out.println("--------------"); for (Object observer : observers) { ((Observer) observer).response(); } } }
|
- 抽象观察者(
Observer
)角色:是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
- 具体观察者(
Concrete Observer
)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| interface Observer { void response(); }
class ConcreteObserver1 implements Observer { @Override public void response() { System.out.println("具体观察者1作出反应!"); } }
class ConcreteObserver2 implements Observer { @Override public void response() { System.out.println("具体观察者2作出反应!"); } }
|
1 2 3 4 5 6 7 8 9 10
| public class ObserverPattern { public static void main(String[] args) { Subject subject = new ConcreteSubject(); Observer obs1 = new ConcreteObserver1(); Observer obs2 = new ConcreteObserver2(); subject.add(obs1); subject.add(obs2); subject.notifyObserver(); } }
|
实现观察者模式时要注意具体目标对象和具体观察者对象之间不能直接调用,否则将使两者之间紧密耦合起来,这违反了面向对象的设计原则。
扩展(JDK9被废弃)
- 在 Java 中,通过 java.util.Observable 类和java.util.Observer
接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例。
Observable
类:用 Vector
向量存所有要通知的观察者对象,包含3 个方法。
void addObserver(Observer o)
方法:将新的观察者对象添加到向量中。
void notifyObservers(Object arg)
方法:调用向量中的所有观察者对象的update
方法,通知它们数据发生改变。
void setChange()
方法:用来设置一个
boolean
类型的内部标志位,注明目标对象发生了变化。当它为真时,notifyObservers()
才会通知观察者。
- Observer
接口:监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用
void update(Observable o,Object arg)
方法,进行相应的工作。
总结
- 优点
- 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则。
- 目标与观察者之间建立了一套触发机制。
- 缺点
- 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
- 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
- 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
- 使用场景:
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
- 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
- 一个对象必须通知其他对象,而并不知道这些对象是谁。
- 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
- 注意事项:
- JAVA 中已经有了对观察者模式的支持类。
- 避免循环引用。
- 如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。
访问者模式(Vistor)
- 将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。
- 将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。
- 模式意图:主要将数据结构与数据操作分离。
- 解决问题:稳定的数据结构和易变的操作耦合问题。
- 使用时机:需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作“污染”这些对象的类,使用访问者模式将这些封装到类中。
- 解决方案:在被访问的类里面加一个对外提供接待访问者的接口。
- 关键代码:在数据基础类里面有一个方法接受访问者,将自身引用传入访问者。
示例代码(抽象)
- 抽象访问者(
Vistor
)角色:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作visit()
,其参数类型表示了被访问的具体元素。
- 具体访问者(
Concrete Vistor
)角色:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| interface Visitor { void visit(ConcreteElementA element);
void visit(ConcreteElementB element); }
class ConcreteVisitorA implements Visitor { @Override public void visit(ConcreteElementA element) { System.out.println("具体访问者A访问-->" + element.operationA()); }
@Override public void visit(ConcreteElementB element) { System.out.println("具体访问者A访问-->" + element.operationB()); } }
class ConcreteVisitorB implements Visitor { @Override public void visit(ConcreteElementA element) { System.out.println("具体访问者B访问-->" + element.operationA()); }
@Override public void visit(ConcreteElementB element) { System.out.println("具体访问者B访问-->" + element.operationB()); } }
|
- 抽象元素(
Element
)角色:声明一个包含接受操作accept()
的接口,被接受的访问者对象作为accept()
方法的参数。
- 具体元素(
Concrete Element
)角色:实现抽象元素角色提供的accept()
操作,其方法体通常都是visitor.visit(this)
,另外具体元素中可能还包含本身业务逻辑的相关操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| interface Element { void accept(Visitor visitor); }
class ConcreteElementA implements Element { @Override public void accept(Visitor visitor) { visitor.visit(this); }
public String operationA() { return "具体元素A的操作"; } }
class ConcreteElementB implements Element { @Override public void accept(Visitor visitor) { visitor.visit(this); }
public String operationB() { return "具体元素B的操作"; } }
|
- 对象结构(
Object Structure
)角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中所有元素的方法,通常由List
,Set
,Map
等聚合类实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public class ObjectStructure { private List<Element> list = new ArrayList<>();
public void accept(Visitor visitor) { Iterator<Element> i = list.iterator(); while (i.hasNext()) { i.next().accept(visitor); } }
public void add(Element element) { list.add(element); }
public void remove(Element element) { list.remove(element); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class VisitorPattern { public static void main(String[] args) { ObjectStructure os = new ObjectStructure(); os.add(new ConcreteElementA()); os.add(new ConcreteElementB());
Visitor visitor = new ConcreteVisitorA(); os.accept(visitor); System.out.println("-------------------"); visitor = new ConcreteVisitorB(); os.accept(visitor); } }
|
示例代码(例子)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| public abstract class Staff { public String name; public int kpi;
public Staff(String name) { this.name = name; kpi = new Random().nextInt(10); }
public abstract void accept(Visitor visitor); }
public class Engineer extends Staff { public Engineer(String name) { super(name); }
@Override public void accept(Visitor visitor) { visitor.visit(this); }
public int getCodeLines() { return new Random().nextInt(10 * 10000); } }
public class Manager extends Staff {
public Manager(String name) { super(name); }
@Override public void accept(Visitor visitor) { visitor.visit(this); }
public int getProducts() { return new Random().nextInt(10); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public interface Visitor { void visit(Engineer engineer);
void visit(Manager manager); }
public class CEOVisitor implements Visitor {
@Override public void visit(Engineer engineer) { System.out.println("工程师: " + engineer.name + ", KPI: " + engineer.kpi); }
@Override public void visit(Manager manager) { System.out.println("经理: " + manager.name + ", KPI:" + manager.kpi + ", 新产品数量: " + manager.getProducts()); } }
public class CTOVisitor implements Visitor {
@Override public void visit(Engineer engineer) { System.out.println("工程师:" + engineer.name + " 代码行数:" + engineer.getCodeLines()); }
@Override public void visit(Manager manager) { System.out.println("经理:" + manager.name + " 产品数量:" + manager.getProducts()); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class BusinessReport { private List<Staff> mStaffs = new ArrayList<>();
public BusinessReport() { mStaffs.add(new Manager("经理-A")); mStaffs.add(new Engineer("工程师-A")); mStaffs.add(new Engineer("工程师-B")); mStaffs.add(new Engineer("工程师-C")); mStaffs.add(new Manager("经理-B")); mStaffs.add(new Engineer("工程师-D")); }
public void showReport(Visitor visitor) { for (Staff staff : mStaffs) { staff.accept(visitor); } } }
|
1 2 3 4 5 6 7 8 9 10
| public class Client { public static void main(String[] args) { BusinessReport report = new BusinessReport(); System.out.println("========CEO看报表========"); report.showReport(new CEOVisitor()); System.out.println("========CTO看报表========"); report.showReport(new CTOVisitor()); } }
|
总结
- 缺点
- 具体元素对访问者公布细节,违反了迪米特原则。
- 具体元素变更比较困难。
- 违反了依赖倒置原则,依赖了具体类,没有依赖抽象。
- 使用场景:
- 对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作
- 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作“污染”这些对象的类,也不希望在增加新操作时修改这些类。
- 注意事项:
- 访问者可以对功能进行统一,可以做报表、UI、拦截器与过滤器。