快学设计模式
创建型模式 (Creational Patterns)
创建型设计模式提供各种对象创建机制。
单例模式 (Singleton pattern)
单例模式主要解决以下问题:
- 保证一个类只有一个实例
- 类如何控制其实例化
- 限制类的实例数量
单例模式的解决方案:
- 私有化对象的构造函数
- 定义静态方法返回类的唯一实例
// 饿汉式 public final class Singleton { private static final Singleton INSTANCE = new Singleton(); private Singleton() {} public static Singleton getInstance() { return INSTANCE; } } // 懒汉式 public final class Singleton { private Singleton() {} private static class SingletonHolder { static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.INSTANCE; } }
结构型模式 (Structural Patterns)
结构型设计模式通过确定一个简单的方式来实现实体之间的关系从而简化设计。
适配器模式 (Adapter pattern)
适配器模式允许一个现存类的接口被用作另一个接口,它让现存类能与其他类工作,而不必修改其源代码。例如:一个适配器可以将一个XML文档的文档对象模型转化为一个树结构进行展示。
适配器模式主要解决以下问题:
- 重用没有客户端所需接口的类
- 让接口不兼容的类一起工作
- 为现存类提供替代接口
通常一个现存的类因为它的接口不符合客户需要的接口从而无法被重用。
适配器模式的解决方案:
- 定义一个分离的 adapter 类,将一个类(adaptee)的不兼容接口转化为客户所有需要的接口(target)。
- 通过 adapter 复用哪些没有所需接口的类
interface Shape { void draw(int x, int y, int z, int j); } class Line { public void draw(int x1, int y1, int x2, int y2) { System.out.println("Line from point A(" + x1 + ";" + y1 + "), to point B(" + x2 + ";" + y2 + ")"); } } class Rectangle { public void draw(int x, int y, int width, int height) { System.out.println("Rectangle with coordinate left-down point (" + x + ";" + y + "), width: " + width + ", height: " + height); } } class LineAdapter implements Shape { private Line adaptee; public LineAdapter(Line line) { this.adaptee = line; } @Override public void draw(int x1, int y1, int x2, int y2) { adaptee.draw(x1, y1, x2, y2); } } class RectangleAdapter implements Shape { private Rectangle adaptee; public RectangleAdapter(Rectangle rectangle) { this.adaptee = rectangle; } @Override public void draw(int x1, int y1, int x2, int y2) { int x = Math.min(x1, x2); int y = Math.min(y1, y2); int width = Math.abs(x2 - x1); int height = Math.abs(y2 - y1); adaptee.draw(x, y, width, height); } } public class AdapterDemo { public static void main(String[] args) { Shape[] shapes = {new RectangleAdapter(new Rectangle()), new LineAdapter(new Line())}; int x1 = 10, y1 = 20; int x2 = 30, y2 = 60; for (Shape shape : shapes) { shape.draw(x1, y1, x2, y2); } } }
装饰器模式 (Decorator pattern)
装饰器模式主要解决以下问题:
- 运行时动态添加和删除功能
- 替代子类方式扩展功能
装饰器模式的解决方案:
- 定义装饰器对象
- 实现被装饰对象的接口,将请求转发给被装饰对象
- 在转发请求之前添加额外的功能
装饰器模式通过不同的装饰器对象在运行时动态扩展对象的功能。
interface Shape { void draw(); } class Rectangle implements Shape { @Override public void draw() { System.out.println("Shape: Rectangle"); } } abstract class ShapeDecorator implements Shape { protected Shape decoratedShape; public ShapeDecorator(Shape decoratedShape){ this.decoratedShape = decoratedShape; } public void draw(){ decoratedShape.draw(); } } class RedShapeDecorator extends ShapeDecorator { @Override public void draw() { decoratedShape.draw(); System.out.println("Border Color: Red"); } } public class DecoratorPatternDemo { public static void main(String[] args) { Shape redRectangle = new RedShapeDecorator(new Rectangle()); System.out.println("\nRectangle of red border"); redRectangle.draw(); } }
外观模式 (Facade pattern)
外观模式主要解决以下问题:
- 为了使子系统更容易使用,为子系统的一组接口提供简单的接口
- 子系统之间的依赖应该最小化
直接访问子系统的客户端直接引用许多拥有不同接口的对象将使客户端难于实现、改变、测试以及重用。
外观模式的解决方案:
- 定义一个外观对象
- 实现子系统的简单接口,将请求委托给底层接口
- 可能在转发请求前后添加额外的功能
外观模式通过与外观对象工作最小化与子系统的依赖。其常用于:
- 通过简单的接口访问复杂系统
- 分层软件的入口点
- 紧耦合子系统的抽象
例子
// 复杂系统 class CPU { public void freeze() {} public void jump(long position){} public void execute() {} } class HardDrive { public byte[] read(long lba, int size){} } class Memory { public void load(long position, byte[] data){} } // Facade class ComputerFacade { private final CPU processor; private final Memory ram; private final HardDrive hd; public ComputerFacade() { this.processor = new CPU(); this.ram = new Memory(); this.hd = new HardDrive(); } public void start() { processor.freeze(); ram.load(BOOT_ADDRESS, hd.read(BOOT_SECTOR, SECTOR_SIZE)); processor.jump(BOOT_ADDRESS); processor.execute(); } } class Client { public static void main(String[] args) { ComputerFacade computer = new ComputerFacade(); computer.start(); } }
组合模式 (Composite pattern)
组合模式主要解决以下问题:
- 层次表示部分-整体结构,因此客户端可以统一处理部分和整体对象
- 部分-整体层次结构应该表示为树结构
组合模式的解决方案:
- 为叶子(部分)对象和组合(整体)对象定义统一组件接口
- 叶子对象直接实现组件接口,组合对象将请求转发给他们的子组件
如果用户发现他们以相同的方式使用多个对象, 并且通常使用几乎相同的代码来处理每个对象, 那么组合模式是一个很好的选择。
例子
import java.util.ArrayList; // Component interface Graphic { public void print(); } // Composite class CompositeGraphic implements Graphic { private final ArrayList<Graphic> childGraphics = new ArrayList<>(); public void add(Graphic graphic) { childGraphics.add(graphic); } public void print() { for (Graphic graphic : childGraphics) { graphic.print(); } } } public class CompositeDemo { public static void main(String[] args) { CompositeGraphic graphic = new CompositeGraphic(); graphic.add(()->System.out.println("Ellipse")); graphic.add(()->System.out.println("Ellipse")); graphic.add(()->System.out.println("Ellipse")); CompositeGraphic graphic1 = new CompositeGraphic(); graphic1.add(()->System.out.println("Ellipse")); CompositeGraphic graphic2 = new CompositeGraphic(); graphic2.add(graphic); graphic2.add(graphic1); graphic2.print() } }
行为型模式 (Behavioral Patterns)
行为型设计模式识别对象之间的常见通信模式并实现这些模式。通过这样做, 这些模式增加了进行这种沟通的灵活性。
观察者模式 (Observer pattern)
观察者模式主要解决以下问题:
- 在不使对象紧耦合的情况下定义对象之间一对多的依赖关系
- 一个对象状态改变时自动更新不限数量的相关对象
- 一个对象可以通知不限数量的其他对象
例如 MVC 设计模式的 View 层就利用了观察者模式,将数据模型交由Subject(M 被观察对象),将视图功能委派给Observer(V 观察者)。
观察者模式的解决方案:
- 定义
Subject
和Observer
对象 - 当
Subject
改变状态时,自动通知和更新已注册的Observer
对象
Subject职责在于维护一个Observer列表,以及通知他们状态的改变。Observer则将自己注册给一个Subject对象,并在收到通知时更新自己的状态。该设计模式将Subject 和Observer解耦合,Subject和Observer对彼此没有明确的了解,可以在运行时独立的增加和删除Observer。
例子
下面是一个用 Java 编写的示例,它接受键盘输入并将每个输入行视为事件。当从System.in
获取一个字符串时,就会以调用观察者 update
方法的形式通知事件给所有的观察者。
import java.util.ArrayList; import java.util.Scanner; public class EventSource { @FunctionalInterface public interface Observer { void update(String event); } private final ArrayList<Observer> observers = new ArrayList<>(); private void notifyObservers(String event) { observers.forEach(observer -> observer.update(event)); } public void addObserver(Observer observer) { observers.add(observer); } public void scanSystemIn() { Scanner scanner = new Scanner(System.in); while (scanner.hasNextLine()) { String line = scanner.nextLine(); notifyObservers(line); } } }
public class ObserverDemo { public static void main(String[] args) { System.out.println("Enter Text:"); EventSource eventSource = new EventSource(); eventSource.addObserver(event -> System.out.println("Observer 1 receive: " + event)); eventSource.addObserver(event -> System.out.println("Observer 2 receive: " + event)); eventSource.scanSystemIn(); } } /* Enter Text: What's your name? Observer 1 receive: What's your name? Observer 2 receive: What's your name? */
命令模式 (Command pattern)
命令模式主要解决以下问题:
- 避免请求的调用程序和特定的请求耦合。
- 为对象配置请求信息
直接将请求实现到类中是不灵活的, 因为它在编译时将类与特定请求耦合在一起, 这使得在运行时无法指定请求。
命令模式的解决方案:
- 定义封装请求的命令对象
- 类将请求委托给一个命令对象而不是直接实现特定的请求。
以上则让用户可以为类配置命令对象,用于执行相应的请求。
例子
一个简单的开关,可以给其配置两个命令:开灯以及关灯。命令模式的优势可以让这个开关用于任何设备,而不仅仅是开灯关灯。
import java.util.HashMap; // 命令接口 interface Command { void execute(); } // invoker class class Switch { private final HashMap<String, Command> commandMap = new HashMap<>(); public void register(String commandName, Command command) { commandMap.put(commandName, command); } public void execute(String commandName) { Command command = commandMap.get(commandName); if (command == null) { throw new IllegalStateException("no command registered for " + commandName); } command.execute(); } } // receiver class class Light { public void turnOn() { System.out.println("The light is on"); } public void turnOff() { System.out.println("The light is off"); } } public class CommandDemo { public static void main(final string[] arguments) { Light lamp = new Light(); Command switchOn = lamp::turnOn; Command switchOff = lamp::trunOff; Switch mySwitch = new Switch(); mySwitch.register("on", switchOn); mySwitch.register("off", switchOff); mySwitch.execute("on"); mySwitch.execute("off"); } }
迭代器模式 (Iterator pattern)
迭代器模式主要解决以下问题:
- 聚合对象不用暴露其数据结构就可以遍历访问其元素
- 在不更改聚合对象接口的情况下更新其遍历方式
在聚合接口中定义访问和遍历操作是不灵活的, 因为它将聚合对象绑定了特定的访问和遍历操作, 并且以后不可能在不更改聚合接口的情况下添加新操作。
迭代器模式的解决方案:
- 定义独立的迭代器对象,其封装了访问和遍历聚合对象的操作。
- 客户端使用迭代器去访问和遍历聚合对象
可以使用不同的迭代器以不同的方式访问和遍历聚合。可以通过定义新的迭代器定义新的访问和遍历操作。
import java.util.Iterator; import java.util.Set; class Family<E> implements Iterable<E> { private final Set<E> elements; public Family(Set<E> elements) { this.elements = Set.copyOf(elements); } @Override public Iterator<E> iterator() { return elements.iterator(); } } public class IterableExample { public static void main(String[] args) { var weasleys = Set.of( "Arthur", "Molly", "Bill", "Charlie", "Percy", "Fred", "George", "Ron", "Ginny" ); var family = new Family<>(weasleys); for (var name : family) { System.out.println(name + " Weasley"); } } } /* Ron Weasley Molly Weasley Percy Weasley Fred Weasley Charlie Weasley George Weasley Arthur Weasley Ginny Weasley Bill Weasley */
状态模式 (State pattern)
状态模式主要解决以下问题:
- 对象的内部状态改变时其应该改变其行为
- 具体的状态行为应该独立定义。也就是说,添加新的状态不应该影响现存状态的行为。
直接在类中实现特定于状态的行为是不灵活的,因为其将类绑定了特定的行为,其让不更改类就添加新的状态以及更改现有状态的行为变得不可能。
状态模式的解决方案:
- 定义独立的状态对象,其封装相应的行为。
- 类将特定于状态的行为委托给其当前状态对象, 而不是直接实现特定于状态的行为。
例子
interface State { void writeName(String name); }
class StateContext { private State state; void setState(State newState) { state = newState; } public void writeName(String name) { state.writeName(name); } }
public class StateDemo { public static void main(String[] args) { final StateContext context = new StateContext(); context.setState((name) -> System.out.println(name.toLowerCase())); context.writeName("Monday"); context.writeName("Tuesday"); context.writeName("Wednesday"); context.writeName("Thursday"); context.setState((name) -> System.out.println(name.toUpperCase())); context.writeName("Friday"); context.writeName("Saturday"); context.writeName("Sunday"); } } /* monday tuesday wednesday thursday FRIDAY SATURDAY SUNDAY */