创建型
把对象的创建和使用相分离
1 工厂方法
定义工厂接口和产品接口,但如何创建实际工厂和实际产品被推迟到子类实现,从而使调用方只和抽象工厂与抽象产品打交道
调用方尽量持有接口或抽象类,避免持有具体类型的子类,以便工厂方法能随时切换不同的子类返回,却不影响调用方代码。
好处:屏蔽创建产品的细节,可能创建新产品,也可能返回缓存
// 抽象工厂 - 接口,返回抽象产品Number
public interface NumberFactory {
// 创建方法:
Number parse(String s);
// 获取工厂实例:
static NumberFactory getFactory() {
return impl;
}
static NumberFactory impl = new NumberFactoryImpl();
}
// 工厂 - (接口)实现类,返回实际产品
public class NumberFactoryImpl implements NumberFactory {
public Number parse(String s) {
return new BigDecimal(s);
}
}
// 客户端用接口获得抽象产品
NumberFactory factory = NumberFactory.getFactory();
Number result = factory.parse("123.456");
静态工厂方法:使用静态方法创建产品
public final class Integer {
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
...
}
Integer n = Integer.valueOf(100);
List<String> list = List.of("A", "B", "C");
2 抽象工厂
抽象工厂会对应到多个实际工厂,每个实际工厂负责创建多个实际产品
// 抽象工厂 接口
// 实际工厂 实现类
// 抽象商品 接口
// 实际商品 实现类
// 客户端创建抽象工厂【类型是实际工厂】,用抽象工厂获得抽象产品
AbstractFactory factory = new FastFactory();
// 生成Html文档:
HtmlDocument html = factory.createHtml("#Hello\nHello, world!");
html.save(Paths.get(".", "fast.html"));
// 生成Word文档:
WordDocument word = factory.createWord("#Hello\nHello, world!");
word.save(Paths.get(".", "fast.doc"));
// 如果把创建工厂的代码放到AbstractFactory中,就可以连实际工厂也屏蔽了:
public interface AbstractFactory {
public static AbstractFactory createFactory(String name) {
if (name.equalsIgnoreCase("fast")) {
return new FastFactory();
} else if (name.equalsIgnoreCase("good")) {
return new GoodFactory();
} else {
throw new IllegalArgumentException("Invalid factory name");
}
}
}
3 生成器/建造者
Builder.build()
使用多个“小型”工厂来最终创建出一个完整对象。多个步骤组装成完整对象。
简化Builder模式,以链式调用的方式来创建对象
4 原型
原型模式,即Prototype,是指创建新对象的时候,根据现有的一个原型来创建。(封装创建过程)
存储简单类型的“值”对象可以复制
// 类中添加一个clone或者copy方法
public Object clone() {
Student std = new Student();
std.id = this.id;
std.name = this.name;
std.score = this.score;
return std;
}
public Student copy() {
Student std = new Student();
std.id = this.id;
std.name = this.name;
std.score = this.score;
return std;
}
5 单例
- 只有
private
构造方法,确保外部无法实例化; - 通过
private static
变量持有唯一实例,保证全局唯一性; - 通过
public static
方法返回此唯一实例,使外部调用方能获取到实例。
提供一个静态方法,直接返回实例;或者直接把static
变量暴露给外部
public class Singleton {
// 静态字段引用唯一实例:
private static final Singleton INSTANCE = new Singleton();
// 通过静态方法返回实例:
public static Singleton getInstance() {
return INSTANCE;
}
// private构造方法保证外部无法实例化:
private Singleton() {
}
}
多线程时不用延迟加载写法。
另一种实现Singleton的方式是利用Java的enum
,因为Java保证枚举类的每个枚举都是单例,所以我们只需要编写一个只有一个枚举的类即可。
使用枚举实现Singleton还避免了第一种方式实现Singleton的一个潜在问题:即序列化和反序列化会绕过普通类的private
构造方法从而创建出多个实例,而枚举类就没有这个问题。
很多程序,尤其是Web程序,大部分服务类都应该被视作Singleton,如果全部按Singleton的写法写,会非常麻烦,所以,通常是通过约定让框架(例如Spring)来实例化这些类,保证只有一个实例,调用方自觉通过框架获取实例而不是new
操作符。
@Component // 表示一个单例组件
因此,除非确有必要,否则Singleton模式一般以“约定”为主,不会刻意实现它。
结构型
组合各种对象。组合与运行期的动态组合。
1 适配器
Adapter模式可以将一个A接口转换为B接口,使得新的对象符合B接口规范。
public BAdapter implements B {
private A a;
public BAdapter(A a) {
this.a = a;
}
public void b() {
a.a();
}
}
在Adapter内部将B接口的调用“转换”为对A接口的调用。
2 桥接
桥接模式实现比较复杂,实际应用也非常少,但它提供的设计思想值得借鉴,即不要过度使用继承,而是优先拆分某些部件,使用组合的方式来扩展功能。
// 抽象类,内部组件是引用一个接口
public abstract class Car {
// 引用Engine:
protected Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
public abstract void drive();
}
// 引擎接口,每一种引擎实现接口
public interface Engine {
void start();
}
// 修正的抽象类,可以定义额外操作
public abstract class RefinedCar extends Car {
public RefinedCar(Engine engine) {
super(engine);
}
public void drive() {
this.engine.start();
System.out.println("Drive " + getBrand() + " car...");
}
public abstract String getBrand();
}
// 实现类继承 修正的抽象类
public class BossCar extends RefinedCar {
public BossCar(Engine engine) {
super(engine);
}
public String getBrand() {
return "Boss";
}
}
// 客户端通过自己选择一个品牌,再配合一种引擎,得到最终的Car
RefinedCar car = new BossCar(new HybridEngine());
car.drive();
┌───────────┐
│ Car │
└───────────┘
▲
│
┌───────────┐ ┌─────────┐
│RefinedCar │ ─ ─ ─>│ Engine │
└───────────┘ └─────────┘
▲ ▲
┌────────┼────────┐ │ ┌──────────────┐
│ │ │ ├─│ FuelEngine │
┌───────┐┌───────┐┌───────┐ │ └──────────────┘
│BigCar ││TinyCar││BossCar│ │ ┌──────────────┐
└───────┘└───────┘└───────┘ ├─│ElectricEngine│
│ └──────────────┘
│ ┌──────────────┐
└─│ HybridEngine │
└──────────────┘
3 组合
树结构。叶子节点统一到一个接口。
┌───────────┐
│ Node │
└───────────┘
▲
┌────────────┼────────────┐
│ │ │
┌───────────┐┌───────────┐┌───────────┐
│ElementNode││ TextNode ││CommentNode│
└───────────┘└───────────┘└───────────┘
使用node 操作
Node root = new ElementNode("school");
root.add(new ElementNode("classA")
.add(new TextNode("Tom"))
.add(new TextNode("Alice")));
root.add(new ElementNode("classB")
.add(new TextNode("Bob"))
.add(new TextNode("Grace"))
.add(new CommentNode("comment...")));
System.out.println(root.toXml());
4 装饰器
把核心功能和附加功能给分开了。在运行期动态给某个对象的实例增加功能的方法。
IO
- 最顶层的Component是接口,对应到IO的就是InputStream这个抽象类。
- ComponentA、ComponentB是实际的子类,对应到IO的就是FileInputStream、ServletInputStream这些数据源。
- Decorator是用于实现各个附加功能的抽象装饰器,对应到IO的就是FilterInputStream。
- 从Decorator派生的就是一个一个的装饰器,它们每个都有独立的功能,对应到IO的就是BufferedInputStream、GZIPInputStream等。
┌───────────┐
│ Component │
└───────────┘
▲
┌────────────┼─────────────────┐
│ │ │
┌───────────┐┌───────────┐ ┌───────────┐
│ComponentA ││ComponentB │... │ Decorator │
└───────────┘└───────────┘ └───────────┘
▲
┌──────┴──────┐
│ │
┌───────────┐ ┌───────────┐
│DecoratorA │ │DecoratorB │...
└───────────┘ └───────────┘
与桥接的区别:
装饰器增加的是功能,功能不是单一的,可以同时存在。所以功能可以一层一层的附加。桥接增加的是某个组件的属性,多种可选一个,是确定的。
装饰器和桥接的目的都是降低继承中衍生的子类的数量,
桥接是通过把一个组件及其子类作为另一总体的字段引用实现功能组合,也可以用多个组件来拼合总体
装饰器则在大类下创建一个装饰器的子族,不管是主要部件还是装饰器都隶属于这个大类,所以装饰器可以不断嵌套
看起来桥接和装饰器的使用都是层层调用,但是两者的功能不同,桥接的子类是负责总体的局部功能,是构成性的
而装饰器则是对已经具有了完整功能的总体进行修饰,是附加性的。
相当于雪中送炭和锦上添花的区别。
5 外观
中介。客户端只和中介打交道。
创建一个中介类。中介类去走所有的流程。
6 享元
一经创建就不可变的对象实例,直接向调用方返回一个共享的实例就行
包装类型如Byte
、Integer
都是不变类
享元模式就是通过工厂方法创建对象,在工厂方法内部,很可能返回缓存的实例,而不是新创建实例,从而实现不可变实例的复用。
在实际应用中,享元模式主要应用于缓存,即客户端如果重复请求某些对象,不必每次查询数据库或者读取文件,而是直接返回内存中缓存的数据。
7 代理
代理模式通过封装一个已有接口,并向调用方返回相同的接口类型,能让调用方在不改变任何代码的前提下增强某些功能(例如,鉴权、延迟加载、连接池复用等)。
使用Proxy模式要求调用方持有接口,作为Proxy的类也必须实现相同的接口类型。
和外观中介的区别
和适配器的区别:还是把A接口转成A接口,加入额外代码以实现权限检查
用Proxy实现这个权限检查,我们可以获得更清晰、更简洁的代码:
- A接口:只定义接口;
- ABusiness类:只实现A接口的业务逻辑;
- APermissionProxy类:只实现A接口的权限检查代理。
jbdc这块看不懂……
行为型
描述一组对象应该如何协作来完成一个整体任务
1 责任链
责任链模式是一种把多个处理器组合在一起,依次处理请求的模式;
责任链模式的好处是添加新的处理器或者重新排列处理器非常容易;
责任链模式经常用在拦截、预处理请求等。
顺序重要
// 请求对象 类
// 处理器 接口
public interface Handler {
// 返回Boolean.TRUE = 成功
// 返回Boolean.FALSE = 拒绝
// 返回null = 交下一个处理
Boolean process(Request request);
}
// 多个处理器 实现类
// 责任链 类,组合Handler
public class HandlerChain {
// 持有所有Handler:
private List<Handler> handlers = new ArrayList<>();
public void addHandler(Handler handler) {
this.handlers.add(handler);
}
public boolean process(Request request) {
// 依次调用每个Handler:
for (Handler handler : handlers) {
Boolean r = handler.process(request);
if (r != null) {
// 如果返回TRUE或FALSE,处理结束:
System.out.println(request + " " + (r ? "Approved by " : "Denied by ") + handler.getClass().getSimpleName());
return r;
}
}
throw new RuntimeException("Could not handle request: " + request);
}
}
// 构造责任链:
HandlerChain chain = new HandlerChain();
chain.addHandler(new ManagerHandler());
chain.addHandler(new DirectorHandler());
chain.addHandler(new CEOHandler());
// 处理请求:
chain.process(new Request("Bob", new BigDecimal("123.45")));
chain.process(new Request("Alice", new BigDecimal("1234.56")));
chain.process(new Request("Bill", new BigDecimal("12345.67")));
chain.process(new Request("John", new BigDecimal("123456.78")));
有些责任链的实现方式是通过某个Handler
手动调用下一个Handler
来传递Request
还有一些责任链模式,每个Handler
都有机会处理Request
,通常这种责任链被称为拦截器(Interceptor)或者过滤器(Filter),它的目的不是找到某个Handler
处理掉Request
,而是每个Handler
都做一些工作
2 命令
请求封装为对象。客户端不需要了解编辑器的所有接口信息。
// 命令 接口
public interface Command {
void execute();
}
// 派生命令 实现类
public class CopyCommand implements Command {
// 持有执行者对象:
private TextEditor receiver;
public CopyCommand(TextEditor receiver) {
this.receiver = receiver;
}
public void execute() {
receiver.copy();
}
}
// 客户端
TextEditor editor = new TextEditor();
editor.add("Command pattern in text editor.\n");
// 执行一个CopyCommand:
Command copy = new CopyCommand(editor);
copy.execute();
editor.add("----\n");
┌─────────────┐
│ Client │
└─────────────┘
│
│
▼
┌─────────────┐
│ Invoker │
├─────────────┤ ┌───────┐
│List commands│─ ─>│Command│
│invoke(c) │ └───────┘
│undo() │ │ ┌──────────────┐
└─────────────┘ ├─>│ CopyCommand │
│ ├──────────────┤
│ │editor.copy() │─ ┐
│ └──────────────┘
│ │ ┌────────────┐
│ ┌──────────────┐ ─>│ TextEditor │
└─>│ PasteCommand │ │ └────────────┘
├──────────────┤
│editor.paste()│─ ┘
└──────────────┘
3 解释器
解释器模式通过抽象语法树实现对用户输入的解释执行。
解释器模式的实现通常非常复杂,且一般只能解决一类特定问题。
匹配
4 迭代器
顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。
如何实现一个Iterator模式
实现Iterator模式的关键是返回一个Iterator
对象,该对象知道集合的内部结构
public class ReverseArrayCollection<T> implements Iterable<T> {
private T[] array;
public ReverseArrayCollection(T... objs) {
this.array = Arrays.copyOfRange(objs, 0, objs.length);
}
public Iterator<T> iterator() {
return new ReverseIterator();
}
class ReverseIterator implements Iterator<T> {
// 索引位置:
int index;
public ReverseIterator() {
// 创建Iterator时,索引在数组末尾:
this.index = ReverseArrayCollection.this.array.length;
}
public boolean hasNext() {
// 如果索引大于0,那么可以移动到下一个元素(倒序往前移动):
return index > 0;
}
public T next() {
// 将索引移动到下一个元素并返回(倒序往前移动):
index--;
return array[index];
}
}
}
5 中介
中介模式是通过引入一个中介对象,把多边关系变成多个双边关系,从而简化系统组件的交互耦合度。
与外观的区别?
外观的中介指的是客户端只和一个外观类打交道。重在屏蔽处理顺序。(给商家付款)
中介模式指的是处理几个对象之间的多边关系。重在减少组件交互耦合。(几个人之间换钱)
6 备忘录
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。
标准的备忘录模式有这么几种角色:
- Memento:存储的内部状态;
- Originator:创建一个备忘录并设置其状态;
- Caretaker:负责保存备忘录。
实际上:增加getState()
和setState()
就可以了。
7 观察者/订阅发布模式
// 被观察者 引用ProductObserver接口
public class Store {
private List<ProductObserver> observers = new ArrayList<>();
private Map<String, Product> products = new HashMap<>();
// 注册观察者:
public void addObserver(ProductObserver observer) {
this.observers.add(observer);
}
// 取消注册:
public void removeObserver(ProductObserver observer) {
this.observers.remove(observer);
}
public void addNewProduct(String name, double price) {
Product p = new Product(name, price);
products.put(p.getName(), p);
// 通知观察者:
observers.forEach(o -> o.onPublished(p));
}
public void setProductPrice(String name, double price) {
Product p = products.get(name);
p.setPrice(price);
// 通知观察者:
observers.forEach(o -> o.onPriceChanged(p));
}
}
// observer:
Admin a = new Admin();
Customer c = new Customer();
// store:
Store store = new Store();
// 注册观察者:
store.addObserver(a);
store.addObserver(c);
┌─────────┐ ┌───────────────┐
│ Store │─ ─ ─>│ProductObserver│
└─────────┘ └───────────────┘
│ ▲
│
│ ┌─────┴─────┐
▼ │ │
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Product │ │ Admin │ │Customer │ ...
└─────────┘ └─────────┘ └─────────┘
变体
1 被观察者也抽象出接口
public interface ProductObservable { // 注意此处拼写是Observable不是Observer!
void addObserver(ProductObserver observer);
void removeObserver(ProductObserver observer);
}
2 通知抽象为Event对象,统一一种通知方法
广义的观察者模式包括所有消息系统。所谓消息系统,就是把观察者和被观察者完全分离,通过消息系统本身来通知
8 状态
允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
不同状态的逻辑分离到不同的状态类中,从而使得增加新状态更容易。关键在于状态转换。
// 状态 接口
// 不同状态 实现类
// 状态切换
public class BotContext {
private State state = new DisconnectedState();
public String chat(String input) {
if ("hello".equalsIgnoreCase(input)) {
// 收到hello切换到在线状态:
state = new ConnectedState();
return state.init();
} else if ("bye".equalsIgnoreCase(input)) {
/ 收到bye切换到离线状态:
state = new DisconnectedState();
return state.init();
}
return state.reply(input);
}
}
// 客户端
Scanner scanner = new Scanner(System.in);
BotContext bot = new BotContext();
for (;;) {
System.out.print("> ");
String input = scanner.nextLine();
String output = bot.chat(input);
System.out.println(output.isEmpty() ? "(no reply)" : "< " + output);
}
9 策略
允许调用方选择一个算法,从而通过不同策略实现不同的计算结果。
Arrays.sort(array, String::compareToIgnoreCase);
定义策略以及使用策略的上下文
// 策略接口
public interface DiscountStrategy {
// 计算折扣额度:
BigDecimal getDiscount(BigDecimal total);
}
// 各种策略 实现类
public class OverDiscountStrategy implements DiscountStrategy {
public BigDecimal getDiscount(BigDecimal total) {
// 满100减20优惠:
return total.compareTo(BigDecimal.valueOf(100)) >= 0 ? BigDecimal.valueOf(20) : BigDecimal.ZERO;
}
}
// 应用策略
public class DiscountContext {
// 持有某个策略:
private DiscountStrategy strategy = new UserDiscountStrategy();
// 允许客户端设置新策略:
public void setStrategy(DiscountStrategy strategy) {
this.strategy = strategy;
}
public BigDecimal calculatePrice(BigDecimal total) {
return total.subtract(this.strategy.getDiscount(total)).setScale(2);
}
}
// 客户端
DiscountContext ctx = new DiscountContext();
// 默认使用普通会员折扣:
BigDecimal pay1 = ctx.calculatePrice(BigDecimal.valueOf(105));
System.out.println(pay1);
// 使用满减折扣:
ctx.setStrategy(new OverDiscountStrategy());
BigDecimal pay2 = ctx.calculatePrice(BigDecimal.valueOf(105));
System.out.println(pay2);
// 使用Prime会员折扣:
ctx.setStrategy(new PrimeDiscountStrategy());
BigDecimal pay3 = ctx.calculatePrice(BigDecimal.valueOf(105));
System.out.println(pay3);
又会员又满减,考虑设计成装饰器
10 模板方法
定义一个操作的一系列步骤,对于某些暂时确定不下来的步骤,就留给子类去实现好了(用抽象方法),这样不同的子类就可以定义出不同的步骤。
模板方法的核心在于父类定义骨架,子类实现某些细节。
// 骨架,能查找使用缓存
public abstract class AbstractSetting {
public final String getSetting(String key) {
String value = lookupCache(key);
if (value == null) {
value = readFromDatabase(key);
putIntoCache(key, value);
}
return value;
}
protected abstract String lookupCache(String key);
protected abstract void putIntoCache(String key, String value);
}
// 子类继承,用什么做缓存都可以
public class LocalSetting extends AbstractSetting {
private Map<String, String> cache = new HashMap<>();
protected String lookupCache(String key) {
return cache.get(key);
}
protected void putIntoCache(String key, String value) {
cache.put(key, value);
}
}
// 客户端
AbstractSetting setting1 = new LocalSetting();
System.out.println("test = " + setting1.getSetting("test"));
System.out.println("test = " + setting1.getSetting("test"));
为了防止子类重写父类的骨架方法,可以在父类中对骨架方法使用final
。对于需要子类实现的抽象方法,一般声明为protected
,使得这些方法对外部客户端不可见。
11 访问者
一种操作一组对象的操作,它的目的是不改变对象的定义,但允许新增不同的访问者,来定义新的操作。
为了访问比较复杂的数据结构,不去改变数据结构,而是把对数据的操作抽象出来,在“访问”的过程中以回调形式在访问者中处理操作逻辑。如果要新增一组操作,那么只需要增加一个新的访问者。
与观察者区别:文章来源:https://www.toymoban.com/news/detail-826401.html
访问者抽象了访问者的行为。文章来源地址https://www.toymoban.com/news/detail-826401.html
// 访问者 接口
public interface Visitor {
// 访问文件夹:
void visitDir(File dir);
// 访问文件:
void visitFile(File file);
}
// 具体的访问者 实现类
// 持有文件夹和文件的数据结构
public class FileStructure {
// 根目录:
private File path;
public FileStructure(File path) {
this.path = path;
}
public void handle(Visitor visitor) {
scan(this.path, visitor);
}
private void scan(File file, Visitor visitor) {
if (file.isDirectory()) {
// 让访问者处理文件夹:
visitor.visitDir(file);
for (File sub : file.listFiles()) {
// 递归处理子文件夹:
scan(sub, visitor);
}
} else if (file.isFile()) {
// 让访问者处理文件:
visitor.visitFile(file);
}
}
}
// 客户端
FileStructure fs = new FileStructure(new File("."));
fs.handle(new JavaFileVisitor());
到了这里,关于设计模式学习笔记的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!