设计模式概述
设计模式的背景
- 设计面向对象软件比较困难,而设计可以复用的面向对象软件更加困难
- 不是解决任何问题都需要从头做起,最好能复用以往的设计方案经验
- 面向对象软件设计经验需要有一定的模式记录下来,以提供给其他设计者使用,这就产生了设计模式。
什么是设计模式
设计模式 (Design pattern)是一套被反复使用的、经过分类编目的面向对象程序设计经验总结。它是面向对象程序设计中典型问题的解决方案。
GoF模式是四位著名面向对象设计专家 (Elich Gamma、Richard Helm、Ralph Johnson、 John Vlissides) 所提出的23种面向对象程序设计模式。这四个人常被称为Gang of Four,简称GoF。
设计模式作用
- 帮助设计者更快、更好完成面向对象程序设计。
- 提高实现代码编写的规范性,标准化开发软件构件。
- 支持编写可扩展、易维护、好管理的程序代码。
- 让代码更容易被他人理解,确保代码可靠性、重用性。
设计模式类型
序号 |
模式类型 |
说明 |
1 |
创造模式 |
与对象创建有关,提供一种创建对象而隐藏创建逻辑,给出灵活创建对象的解决方案。典型模式有5种。 |
2 |
结构模式 |
处理类或对象的组合,给出利用继承、接口组合对象以获得新功能的解决方案。典型模式有7种。 |
3 |
行为模式 |
用于描述对象之间协作完成特定功能及其职责分配,给出对象之间通信的解决方案。典型模式有11种。 |
创建型模式
创建型模式的关注点是“怎样创建对象?”它的主要特点是“将对象的创建与使用分离”,从而降低对象之间的耦合度。
一、创建型模式类型
- 单例模式 (Singleton Pattern)
- 工厂模式 (Factory Pattern)
- 抽象工厂模式 (Abstract Factory Pattern)
- 建造者模式 (Builder Pattern)
- 原型模式 (Prototype Pattern)
单例模式 (Singleton Pattern)
单例模式是指一个类只能有一个实例,且该类能自行创建这个实例的一种程序设计模式。
模式动机
虽然软件系统可以创建多个进程任务并发运行,但系统在同一时段只允许一个进程任务运行处理。如下一些计算机软件程序需要采用单一进程处理。
- 打印机程序
- Windows 的回收站
- 操作系统中的文件系统
- 多线程中的线程池、数据库的连接池
- 应用程序的对话框
- Web应用的配置对象
模式解决问题
如何让系统只允许唯一的实例进行服务资源访问,从而解决多实例模式带来的系统开销问题。
解决问题思想:
- 单例模式类只允许创建一个实例对象;
- 该实例对象必须在单例模式类初始化时自行创建;
- 单例模式类对外仅提供一个访问该单例的全局访问点。
模式设计方案
- 采用一个类仅能创建一个实例对象。
- 该类需自行创建这个实例对象。
- 该类提供全局公开方法让外部来获取这个实例对象。
模式实现方案
饿汉式单例类:在单例类被初始化加载时,就实例化一个对象交给自己的引用。
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class Singleton {
private static Singleton singletonVar = new Singleton();
private Singleton() { }
public static Singleton getInstance() { return singletonVar; }
}
|
懒汉式单例类:在访问实例对象时才实例化对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class Singleton { private static Singleton singletonVar;
private Singleton() { }
public static synchronized Singleton getInstance() {
if (singletonVar == null) { singletonVar = new Singleton(); } return singletonVar; }
}
|
synchronized关键字的作用是实现线程同步,保证在同一时刻只有一个线程能够访问某个资源。在懒汉式单例类中,getInstance()方法是获取单例对象的唯一入口,因此使用synchronized关键字可以保证在多线程环境下,只有一个线程能够创建单例对象,从而保证单例对象的唯一性。如果不使用synchronized关键字,在多线程环境下,可能会有多个线程同时执行getInstance()方法,从而导致多个单例对象被创建。这将会破坏单例模式的设计意图。
适用场景
- 需要频繁实例化对象,然后销毁对象。
- 创建对象时开销较大,但又需经常使用该对象。
- 有状态的工具类对象。
- 频繁访问的数据库或文件对象。
应用示例——用懒汉式单例模式模拟产生美国当今总统对象
- 设计一个 President单例模式类。该类提供了一个静态方法,供外界获取它的静态实例。
- SingletonLazy类访问 President单例类来获取 President对象。
SingletonLazy类编程代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class SingletonLazy {
public static void main(String[] args) { President zt1 = President.getInstance(); zt1.getName(); President zt2 = President.getInstance(); zt2.getName(); if (zt1 == zt2) { System.out.println("他们是同一人!"); } else { System.out.println("他们不是同一人!"); } } }
|
President类编程代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class President { private static volatile President instance = null;
private President() { System.out.println("产生一个总统!"); }
public static synchronized President getInstance() { if (instance == null) { instance = new President(); } else { System.out.println("已经有一个总统,不能产生新总统!"); } return instance; }
public void getName() { 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
| public class SingletonLazy {
public static void main(String[] args) { President zt1 = President.getInstance(); zt1.getName(); President zt2 = President.getInstance(); zt2.getName(); if (zt1 == zt2) { System.out.println("他们是同一人!"); } else { System.out.println("他们不是同一人!"); } } }
class President { private static volatile President instance = null;
private President() { System.out.println("产生一个总统!"); }
public static synchronized President getInstance() { if (instance == null) { instance = new President(); } else { System.out.println("已经有一个总统,不能产生新总统!"); } return instance; }
public void getName() { System.out.println("我是美国总统:拜登。"); } }
|
运行结果:
模式优缺点
优点:
- 在内存中一个类只有一个实例,可减少程序占用内存的开销。
- 避免频繁的创建和销毁实例,可以提高软件程序运行性能。
- 避免对资源的多重占用。
- 提供全局访问点,可以共享资源访问。
缺点:
- 单例模式一般没有接口,扩展困难。如果要扩展功能,需修改原来的代码,这违背开闭原则。
- 在并发编程中,单例模式不利于代码调试,因在单例中的代码没有执行完,不能模拟生成一个新的对象。
- 单例模式的功能代码通常写在一个类中。如果功能设计不合理,则很容易违背单一职责原则。
针对如下民政服务系统的婚姻登记功能设计类图,如何采用饿汉式单例模式实现Java程序编写,并在主程序中输出消息反馈。
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
| class MarriageRegister {
private static final MarriageRegister instance = new MarriageRegister();
private MarriageRegister() { }
public static MarriageRegister getInstance() { return instance; }
public void showMessage() { System.out.println("Marriage registration is successful."); } }
public class Client {
public static void main(String[] args) { MarriageRegister register = MarriageRegister.getInstance();
register.showMessage(); } }
|
运行结果:
结构型模式
结构型模式描述如何将类或对象组成更大的结构实现特定功能特性。它分为类结构型模式和对象结构型模式。前者采用继承机制来组织接口和类,后者采用组合或聚合来组织对象。
结构型模式类型
- 适配器模式 (Adapter Pattern)
- 桥接模式 (Bridge Pattern)
- 装饰器模式 (Decorator Pattern)
- 外观模式 (Facade Pattern)
- 组合模式 (Composite Pattern)
- 享元模式 (Flyweight Pattern)
- 代理模式 (Proxy Pattern)
适配器模式 (Adapter Pattern)
适配器模式实现不同类接口之间的转换,使接口不兼容的那些类也能一起工作。
模式动机
当利用现有的类库实现系统的新功能开发时,发现当前系统的接口与现有类库规范不兼容。如果重新开发这些类成本又很高,这时可以使用适配器模式来解决现有类的复用问题。
解决主要问题
模式设计方案
- 目标接口 (Target) : 当前系统业务所期待使用的接口。
- 适配者类 (Adaptee):它是被访问和适配的现有组件类.
- 适配器类 (classAdapter ) : 它是个转换器,通过继承适配者和实现目标接口,让客户端类可通过目标接口访问适配者。
模式解决方案
1 2 3 4 5 6 7 8 9 10 11 12 13
| package adapter;
interface Target { public void request(); }
class Adaptee { public void specificRequest() { System.out.println("适配者中的业务代码被调用!"); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class ClassAdapter extends Adaptee implements Target { public void request() { specificRequest(); } }
public class ClassAdapterTest { public static void main(String[] args) { System.out.println("类适配器模式测试:"); Target target = new ClassAdapter(); target.request(); } }
|
运行输出结果:
适用场景
- 不想修改原有代码而重用现有的类功能。
- 将一个类的接口转换成客户希望的另外一个接口,使得原本不兼容的那些类|能一起工作。
- 使用第三方提供的类,但类接口定义和自己要求的接口定义不同。
应用示例
为了实现AudioPlayer播放其他格式的音频文件。需要创建一个实现 MediaPlayer接口的适配器类MediaAdapter。该适配器类使用AdvancedMediaPlayer类的对象来播放所需的媒体文件格式。
Audioplayer 使用适配器类MediaAdapter 传递所需的音频类型,它不需要知道能播放所需格式音频的实际类。
AdapterPatternDemo 类使用AudioPlayer 类和适配器类MediaAdapter 来播放各种格式文件。
MediaPlayer接口:
1 2 3 4 5 6
| package AdapterPattern;
public interface MediaPlayer { public void play(String audioType, String fileName); }
|
AdvancedMediaPlayer接口:
1 2 3 4 5 6 7 8
| package AdapterPattern;
public interface AdvancedMediaPlayer { public void playVlc(String fileName);
public void playMp4(String fileName); }
|
VlcPlayer类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package AdapterPattern;
public class VlcPlayer implements AdvancedMediaPlayer { @Override public void playVlc(String fileName) { System.out.println("播放 vlc file. Name: " + fileName); }
@Override public void playMp4(String fileName) { } }
|
Mp4Player类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package AdapterPattern;
public class Mp4Player implements AdvancedMediaPlayer { @Override public void playVlc(String fileName) { }
@Override public void playMp4(String fileName) { System.out.println("播放 mp4 file. Name: " + fileName); } }
|
MediaAdapter类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package AdapterPattern;
public class MediaAdapter implements MediaPlayer { AdvancedMediaPlayer advancedMusicPlayer;
public MediaAdapter(String audioType) { if (audioType.equalsIgnoreCase("vlc")) { advancedMusicPlayer = new VlcPlayer(); } else if (audioType.equalsIgnoreCase("mp4")) { advancedMusicPlayer = new Mp4Player(); } }
@Override public void play(String audioType, String fileName) { if (audioType.equalsIgnoreCase("vlc")) { advancedMusicPlayer.playVlc(fileName); } else if (audioType.equalsIgnoreCase("mp4")) { advancedMusicPlayer.playMp4(fileName); } } }
|
AudioPlayer类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package AdapterPattern;
public class AudioPlayer implements MediaPlayer { MediaAdapter mediaAdapter;
@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 = new MediaAdapter(audioType); mediaAdapter.play(audioType, fileName); } else { System.out.println("Invalid media. " + audioType + " format not supported"); } } }
|
AdapterPatternDemo类:
1 2 3 4 5 6 7 8 9 10 11 12 13
| package AdapterPattern;
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"); } }
|
运行结果:
补:equalsIgnoreCase 是 Java 中的一个方法,用于比较两个字符串是否相等,而忽略它们的大小写。这个方法是 String 类的成员,所以可以对任何字符串对象调用它。
模式优缺点
优点:
- 客户端通过适配器可以透明地调用目标接口。
- 程序员不需要修改原有代码而重用现有的适配者类。
- 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。
- 该模式符合开闭原则。
缺点:
- 适配器编写过程需要结合业务场景全面考虑,可能会增加编程的复杂性。
- 增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得复杂。
分析下面设计类图如何解决新能源汽车的发动机接口?给出Java程序实现该设计功能。
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 54 55 56 57 58 59 60 61 62
| interface Motor { void drive(); }
class ElectricMotor { public void electricDrive() { System.out.println("Electric Motor drive."); } }
class OpticalMotor { public void opticalDrive() { System.out.println("Optical Motor drive."); } }
class ElectricAdapter implements Motor { private ElectricMotor emotor;
public ElectricAdapter() { emotor = new ElectricMotor(); }
public void drive() { emotor.electricDrive(); } }
class OpticalAdapter implements Motor { private OpticalMotor omotor;
public OpticalAdapter() { omotor = new OpticalMotor(); }
public void drive() { omotor.opticalDrive(); } }
class ReadXML { public static Object getObject() { return new ElectricAdapter(); } }
public class MotorAdapterTest { public static void main(String[] args) { Motor motor = (Motor) ReadXML.getObject(); motor.drive(); } }
|
运行结果:
桥接模式 (Bridge Pattern)
桥接模式是一种用于把抽象类与实现类解耦,使得二者都可以实现独立变化的设计模式。它通过在抽象类和实现类之间加入桥接接口,来实现二者的解耦。
模式动机
在一些应用中,某些类具有多个维度的变化,如既可按形状扩展,又可按颜色扩展。如果类采用继承方式实现m种形状和n种颜色进行扩展建模,其子类就有 m×n 种,其扩展较困难。当采用桥接模式可以将抽象类与实现类解耦,使得二者可以独立变化,从而容易实现功能类扩展。
解决问题
- 如何将抽象类与实现类分离,使它们都可以独立地进行变化。
- 在抽象类与实现类都有多种变化情况下,如何解决扩展困难问题。
模式设计方案
- 抽象化角色类 (Abstraction) 维护一个指向Implementor接口的指针。
- 扩展抽象化角色类(RefinedAbstraction )扩充抽象角色类Abstraction功能。
- 接口类 (Implementor)定义实现类的接口。
- 实现类 (ConcretelmpIementor )具体定义实现操作。
模式实现方案
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
| package bridge;
public class BridgeTest { public static void main(String[] args) { Implementor imple = new ConcreteImplementorA(); Abstraction abs = new RefinedAbstraction(imple); abs.Operation(); } }
interface Implementor { public void OperationImpl(); }
class ConcreteImplementorA implements Implementor { public void OperationImpl() { System.out.println("具体实现化(Concrete Implementor)角色被访问"); } }
abstract class Abstraction { protected Implementor imple;
protected Abstraction(Implementor imple) { this.imple = imple; }
public abstract void Operation(); }
class RefinedAbstraction extends Abstraction { protected RefinedAbstraction(Implementor imple) { super(imple); }
public void Operation() { System.out.println("扩充抽象化(Refined Abstraction)角色被访问"); imple.OperationImpl(); } }
|
运行结果:
适用场景
- 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
- 当一个系统不希望使用继承或多层次继承导致子类个数急剧增加。
- 当一个系统需要在构件的抽象化类和具体化实现类之间增加更多的灵活性。
应用示例
- 实现接口 DrawAPI及其实体类RedCircle、GreenCircle。
- Shape 抽象类将使用DrawAPI 接口对象。Circle为扩展抽象类。
- BridgePatternDemo 类 使用 Shape类来画出不同颜色的圆。
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 54 55 56 57 58 59 60
| package BridgePattern;
interface DrawAPI { public void drawCircle(int radius, int x, int y); }
class RedCircle implements DrawAPI { @Override public void drawCircle(int radius, int x, int y) { System.out.println("Drawing Circle[ color: red, radius: " + radius + ", x: " + x + ", y: " + y + "]"); } }
class GreenCircle implements DrawAPI { @Override public void drawCircle(int radius, int x, int y) { System.out.println("Drawing Circle[ color: green, radius: " + radius + ", x: " + x + ", y: " + y + "]"); } }
abstract class Shape { protected DrawAPI drawAPI;
protected Shape(DrawAPI drawAPI) { this.drawAPI = drawAPI; }
public abstract void draw(); }
class Circle extends Shape { private int x, y, radius;
public Circle(int x, int y, int radius, DrawAPI drawAPI) { super(drawAPI); this.x = x; this.y = y; this.radius = radius; }
public void draw() { drawAPI.drawCircle(radius, x, y); } }
public class BridgePatternDemo { public static void main(String[] args) { Shape redCircle = new Circle(100, 100, 10, new RedCircle()); Shape greenCircle = new Circle(100, 100, 10, new GreenCircle());
redCircle.draw(); greenCircle.draw(); } }
|
运行结果:
模式优缺点
优点:
- 抽象类和实现类的分离有助于降低对实现部分编译时的依赖
- 可独立地对抽象类和实现类的层次结构进行扩充
- 实现细节对客户透明
- 符合开闭原则
缺点:
- 桥接模式会增加系统的理解与设计难度
- 要求开发者针对抽象类进行设计与编程
分析如下女士皮包选购功能类图设计如何应用桥接模式?如何编写Java程序实现该功能。
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 54 55 56 57 58 59
| interface Color { String getColor(); }
class Yellow implements Color { public String getColor() { return "yellow"; } }
class Red implements Color { public String getColor() { return "red"; } }
abstract class Bag { protected Color color;
public void setColor(Color color) { this.color = color; }
public abstract String getName(); }
class HandBag extends Bag { public String getName() { return "HandBag"; } }
class Wallet extends Bag { public String getName() { return "Wallet"; } }
public class BagManage { public static void main(String[] args) { Color yellow = new Yellow(); Color red = new Red();
Bag handBag = new HandBag(); handBag.setColor(yellow);
Bag wallet = new Wallet(); wallet.setColor(red);
System.out.println("The " + handBag.getName() + " is " + handBag.color.getColor()); System.out.println("The " + wallet.getName() + " is " + wallet.color.getColor()); } }
|
运行结果:
行为型模式
行为型模式用于解决程序在运行时存在的复杂流程控制,如多个对象之间相互协作完成功能任务,以及如何分配职责。
行为型模式类型
- 责任链模式 (Chain of Responsibility Pattern)
- 命令模式 (Command Pattern)
- 解释器模式 (Interpreter Pattern)
- 迭代器模式 (lterator Pattern)
- 中介者模式(Mediator Pattern)
- 观察者模式(Observer Pattern
- 备忘录模式(Memento Pattern)
责任链模式 (Chain of Responsibility Pattern)
责任链模式是一种通过对象链进行请求处理的设计模式。
模式动机
为了避免请求发送者与多个处理者耦合在一起,仅需将请求发生给责任链首个对象,后续在该链的对象中传递处理,直到有某对象处理它为止。
解决问题
- 客户请求发送到对象责任链后,如何做到无须关心请求的处理细节和请求在对象责任链上的传递过程。
- 对象责任链如何将请求的发送者与处理者进行解耦。
模式设计方案
- 抽象处理者类 (Handler ) : 定义一个处理请求的接口,包含抽象处理方法( handleRequest()) 和一个后继连接(next)。
- 具体处理者类(ConcreteHandler ) : 实现抽象处理者的处理方法,判断自己能否处理本次请求。如果可以处理请求则处理,否则将该请求转给它的后继者。
- 创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
模式实现方案
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
| package chainOfResponsibility;
public class ChainOfResponsibilityPattern { public static void main(String[] args) { Handler handler1 = new ConcreteHandler1(); Handler handler2 = new ConcreteHandler2(); handler1.setNext(handler2); handler1.handleRequest("two"); } }
abstract class Handler { private Handler next;
public void setNext(Handler next) { this.next = next; }
public Handler getNext() { return next; }
public abstract void handleRequest(String request); }
class ConcreteHandler1 extends Handler { public void handleRequest(String request) { if (request.equals("one")) { System.out.println("具体处理者1负责处理请求!"); } else { if (getNext() != null) { getNext().handleRequest(request); } else { System.out.println("没有人处理该请求!"); } } } }
class ConcreteHandler2 extends Handler { public void handleRequest(String request) { if (request.equals("two")) { System.out.println("具体处理者2负责处理请求!"); } else { if (getNext() != null) { getNext().handleRequest(request); } else { System.out.println("没有人处理该请求!"); } } } }
|
运行结果:
适用场景
- 有多个对象处理同一个请求,具体哪个对象处理该请求由运行时刻根据消息内容自动确定。
- 在不明确指定接收者的情况下,向多个对象中的首个对象提交请求。
- 可动态指定一组对象处理请求,或添加新的处理者。
应用示例
- 创建抽象类AbstractLogger,记录日志信息。
- 创建三种日志类型的记录器,分别继承AbstractLogger抽象类。
- 每个记录器接收消息后,判断该消息是否属于自己的类型。如果是则相应地打印出来。否则将不打印,并把消息传给下一个记录器。
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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| package ChainResponsibilityPattern;
abstract class AbstractLogger { public static int INFO = 1; public static int DEBUG = 2; public static int ERROR = 3;
protected int level; protected AbstractLogger nextLogger;
public void setNextLogger(AbstractLogger nextLogger) { this.nextLogger = nextLogger; }
public void logMessage(int level, String message) { if (this.level <= level) { write(message); } if (nextLogger != null) { nextLogger.logMessage(level, message); } }
abstract protected void write(String message); }
class ConsoleLogger extends AbstractLogger { public ConsoleLogger(int level) { this.level = level; }
@Override protected void write(String message) { System.out.println("Standard Console::Logger: " + message); } }
class ErrorLogger extends AbstractLogger { public ErrorLogger(int level) { this.level = level; }
@Override protected void write(String message) { System.out.println("Error Console::Logger: " + message); } }
class FileLogger extends AbstractLogger { public FileLogger(int level) { this.level = level; }
@Override protected void write(String message) { System.out.println("File::Logger: " + message); } }
public class ChainPatternDemo { private static AbstractLogger getChainOfLoggers() { AbstractLogger errorLogger = new ErrorLogger(AbstractLogger.ERROR); AbstractLogger fileLogger = new FileLogger(AbstractLogger.DEBUG); AbstractLogger consoleLogger = new ConsoleLogger(AbstractLogger.INFO);
errorLogger.setNextLogger(fileLogger); fileLogger.setNextLogger(consoleLogger);
return errorLogger; }
public static void main(String[] args) { AbstractLogger loggerChain = getChainOfLoggers();
loggerChain.logMessage(AbstractLogger.INFO, "这是一个信息日志信息。"); loggerChain.logMessage(AbstractLogger.DEBUG, "这是一个 debug 日志信息。"); loggerChain.logMessage(AbstractLogger.ERROR, "这是一个 error 日志信息。"); } }
|
运行结果:
模式优缺点
优点:
降低了请求对象与处理对象之间的耦合度。一个请求对象无须知道到底是哪一个对象理其请求以及链的结构。
可以根据需要增加新的请求处理类,满足开闭原则
当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者if…else 语句。
责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
缺点:
不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
职责链建立的合理性要靠客户端来保证,增加了客户端编程的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。
分析如下请假条审批模块功能类图如何应用责任链模式设计?如何编写Java程序。
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 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| abstract class Leader { private Leader next;
public void setNext(Leader next) { this.next = next; }
public Leader getNext() { return next; }
public abstract void handleRequest(int LeaveDays); }
class ClassAdviser extends Leader { @Override public void handleRequest(int LeaveDays) { if (LeaveDays <= 2) { System.out.println("班主任批准了 " + LeaveDays + " 天的请假。"); } else if (next != null) { next.handleRequest(LeaveDays); } } }
class DepartmentHead extends Leader { @Override public void handleRequest(int LeaveDays) { if (LeaveDays <= 5) { System.out.println("系主任批准了 " + LeaveDays + " 天的请假。"); } else if (next != null) { next.handleRequest(LeaveDays); } } }
class Dean extends Leader { @Override public void handleRequest(int LeaveDays) { if (LeaveDays <= 10) { System.out.println("院长批准了 " + LeaveDays + " 天的请假。"); } else { System.out.println("请假请求 " + LeaveDays + " 天被拒绝。"); } } }
public class LeaveApprovalTest { public static void main(String[] args) { Leader teacher1 = new ClassAdviser(); Leader teacher2 = new DepartmentHead(); Leader teacher3 = new Dean();
teacher1.setNext(teacher2); teacher2.setNext(teacher3);
teacher1.handleRequest(8); } }
|
客户端类也可按如下方式书写:
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 LeaveApprovalTest {
private static Leader getChainOfLeaders() { Leader classAdviser = new ClassAdviser(); Leader departmentHead = new DepartmentHead(); Leader dean = new Dean();
classAdviser.setNext(departmentHead); departmentHead.setNext(dean);
return classAdviser; }
public static void main(String[] args) { Leader chainOfLeaders = getChainOfLeaders();
chainOfLeaders.handleRequest(8); } }
|
班主任可以批准最多2天的请假,系主任可以批准最多5天的请假,院长可以批准最多10天的请假。如果有一个请求是请8天假,班主任会将它传递给系主任,系主任再传递给院长进行批准。如果请求超出了批准能力,就会被拒绝。
中介者模式是一种通过中介对象来封装若干对象之间的交互,实现对象之间的松耦合,并且可以独立地改变它们之间交互的设计模式。
模式动机
在一些应用中,多个对象之间存在较复杂的网状交互关系。如果把这种’网状结构”改为“星形结构”,可降低它们之间的“耦合性”。这时只要找一个“中介者”来实现。
解决主要问题
- 如何解决多个对象之间存在大量的关联关系。
- 若一个对象发生改变,如何跟踪与之相关联的对象,并做出相应的处理。
模式设计方案
抽象中介者 (Mediator) : 提供了同事对象注册与转发同事对象信息的抽象方法。
具体中介者 (Concrete Mediator) :实现抽象中介者类,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系。
抽象同事类(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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| package mediator;
import java.util.*;
public class MediatorPattern { public static void main(String[] args) { Mediator md = new ConcreteMediator(); Colleague c1, c2; c1 = new ConcreteColleague1(); c2 = new ConcreteColleague2(); md.register(c1); md.register(c2); c1.send(); System.out.println("---------------"); c2.send(); } }
abstract class Mediator { public abstract void register(Colleague colleague);
public abstract void relay(Colleague cl); }
class ConcreteMediator extends Mediator { private List<Colleague> colleagues = new ArrayList<Colleague>();
public void register(Colleague colleague) { if (!colleagues.contains(colleague)) { colleagues.add(colleague); colleague.setMedium(this); } }
public void relay(Colleague cl) { for (Colleague ob : colleagues) { if (!ob.equals(cl)) { ((Colleague) ob).receive(); } } } }
abstract class Colleague { protected Mediator mediator;
public void setMedium(Mediator mediator) { this.mediator = mediator; }
public abstract void receive();
public abstract void send(); }
class ConcreteColleague1 extends Colleague { public void receive() { System.out.println("具体同事类1收到请求。"); }
public void send() { System.out.println("具体同事类1发出请求。"); mediator.relay(this); } }
class ConcreteColleague2 extends Colleague { public void receive() { System.out.println("具体同事类2收到请求。"); }
public void send() { System.out.println("具体同事类2发出请求。"); mediator.relay(this); } }
|
运行结果:
适用场景
- 当对象之间存在复杂的网状结构关系而导致通信关系复杂。
- 通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。
应用示例
多个用户可以通过聊天室进行消息交流聊天室向所有的用户显示消息。
创建两个类 ChatRoom 和 User。
User 对象 使用 ChatRoom 对象的showMessage()方法来分享他们的消息。
MediatorPatternDemo演示类使用 User对象来显示他们之间的通信。
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
| package MediatorPattern;
import java.util.Date;
class ChatRoom { public static void showMessage(User user, String message) { System.out.println(new Date().toString() + " [" + user.getName() + "]: " + message); } }
class User { private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public User(String name) { this.name = name; }
public void sendMessage(String message) { ChatRoom.showMessage(this, message); } }
public class MediatorPatternDemo { public static void main(String[] args) { User robert = new User("Robert"); User john = new User("John");
robert.sendMessage("Hi! John!"); john.sendMessage("Hello! Robert!"); } }
|
运行示例:
模式优缺点
优点:
缺点:
- 中介者模式将原本多个对象直接的相互依赖变成了中介者和多个对象之间的依赖关系。
- 当交互的对象越多时,中介者就会越臃肿,变得复杂且难以维护。
分析如下“房地产交流”模块功能类图如何应用中介者模式设计?给出Java程序。
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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
| import java.util.ArrayList; import java.util.List;
interface Medium { void register(Customer member);
void relay(String from, String ad); }
class EstateMedium implements Medium { private List<Customer> members = new ArrayList<>();
@Override public void register(Customer member) { if (!members.contains(member)) { members.add(member); member.setMedium(this); } }
@Override public void relay(String from, String ad) { for (Customer member : members) { if (!member.getName().equals(from)) { member.receive(from, ad); } } } }
abstract class Customer { protected Medium medium; protected String name;
public Customer(String name) { this.name = name; }
public abstract void send(String ad);
public abstract void receive(String from, String ad);
public String getName() { return name; }
public void setMedium(Medium medium) { this.medium = medium; } }
class Seller extends Customer { public Seller(String name) { super(name); }
@Override public void send(String ad) { System.out.println("Seller [" + this.name + "] sends message: " + ad); medium.relay(this.name, ad); }
@Override public void receive(String from, String ad) { System.out.println("Seller [" + this.name + "] received message from " + from + ": " + ad); } }
class Buyer extends Customer { public Buyer(String name) { super(name); }
@Override public void send(String ad) { System.out.println("Buyer [" + this.name + "] sends message: " + ad); medium.relay(this.name, ad); }
@Override public void receive(String from, String ad) { System.out.println("Buyer [" + this.name + "] received message from " + from + ": " + ad); } }
public class MediatorPattern { public static void main(String[] args) { Medium medium = new EstateMedium(); Customer seller = new Seller("Alice"); Customer buyer = new Buyer("Bob");
medium.register(seller); medium.register(buyer);
seller.send("House for sale!"); buyer.send("Looking for a house!"); } }
|
运行结果:
课堂作业与作业练习
一、单选题
1.哪种设计原则要求面向接口编程,而不要面向实现编程?(C)
A.开闭原则
B.里氏替换原则
C.依赖倒置原则
D.接口分离原则
2.哪种设计原则要求没有直接关系的类就不能直接相互调用?(B)
A.里氏替换原则
B.迪米特法则
C. 依赖倒置原则
D.开闭原则
3.下面哪个不是发现类的方法?(C)
A. CRC方法
B.用例驱动法
C.头脑风暴
D.公共类模式法
4.子类组合来自超类的特征,并重载部分继承来的特征,该继承称为什么?(C)
A.扩展继承
B.方便继承
C.限制继承
D.以上都不是
5.下面哪一个操作符用于定义条件片段?(C)
A. opt
B. para
C. alt
D. loop
二、判断题
1.单例模式属于设计模式的行为模式类别。(×)
2.适配器模式可以解决不同模块的类接口转换问题。(√)
3.桥接模式是一种用于把抽象部分与实现部分解耦的设计模式。(√)
4.责任链模式要求请求发送者与多个请求处理者直接交互。(×)
5.中介者模式的动机用于降低多个对象之间存在较复杂的关系。(√)
6.处于相同状态的同类的不同对象对同一事件的反应往往是一样的,而处于不同状态的同一对象则对同一事件会做出不同反应。(√)
7.只要将包中元素的可见性设为公共的,则其他包就可以访问它。(×)
8.聚合与泛化都是面向对象系统支持功能复用的强大技术。(×)
9.在UML构件图中,需要定义消息来描述构件之间的联系。(×)
10.所有对象都通过类来描述,所有类都具有对象。(×)
补充:
6.处于相同状态的对象对同一事件具有同样方式的反应,所以当给定状态下的多个对象当接受到相同事件时会执行相同的动作,例如:当一个在线视频播放器处于”播放中”状态时,对于”暂停”按钮的点击事件,所有这类播放器对象都会执行相同的动作,即暂停当前视频。无论哪个播放器实例,只要它正在播放视频,点击”暂停”按钮都会导致视频停止播放。然而处于不同状态下的对象会通过不同的动作对同一事件做出不同的反应。例如,当自动答复机处于处理事务状态或空闲状态时会对取消键做出不同的反应。
7.虽然将类或成员的可见性设为公共(public)可以让其他包中的类访问这些元素,但这并不是唯一的条件。其他包中的类还需要正确地导入包含公共元素的包。
8.这道题网上搜出来都是对的,答案给的错,后面再看看吧。
10.所有对象都是由类来描述的,这是正确的;但不是所有类都具有对象实例。有些类可能从未被实例化,尤其是抽象类和工具类(可能只有静态方法和成员)。
三、填空题
(状态机图)通过对对象的各种状态建立模型来描述对象随时间变化的动态行为,并且它是以独立的对象为中心进行描述的。
在UML类图中,类用矩形图符来表示,这个矩形由3个部分组成,分别是类型名、(属性)和操作。
UML中的交互图包括顺序图和(通信图)。
UML中顺序图表示为二维图,纵向是对象,横向代表参与交互对象之间的(消息)。
状态机图由对象的状态和连接这些状态的(转换)组成。