1.设计模式六大原则
- 单一职责原则
就一个类而言,应该仅有一个引起它变化的原因。
例如,在 Activity 中不应该存在 Bean 文件、网络数据处理以及列表的 Adapter。 - 开放封闭原则
类、模块、函数等应该是可以扩展的,但是不可修改。
开放封闭原则有两个含义:一个是对于拓展是开放的,另一个是对于修改是封闭的。 - 里氏替换原则
所有引用基类(父类)的地方必须能够透明地使用其子类的对象。 - 依赖倒置原则
高层模块不应该依赖低层模块,两者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。 - 迪米特原则
一个软件实体应当尽可能少地与其他实体发生相互作用。 - 接口隔离原则
一个类对另一个类的依赖应该建立在最小的接口上。
2. 设计模式分类
GoF 提出的设计模式总共有 23 种,根据目的准则分类,分为三大类。
- 创建型设计模式,共 5 种:单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式。
- 结构型设计模式,共 7 种:适配器模式、装饰模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
- 行为型设计模式,共 11 种:策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
另外,随着发展也涌现出很多新的设计模式:规格模式、对象池模式、雇工模式、黑板模式和空对象模式等。
2.1创建型设计模式
2.1.1 单例模式
单例模式的 7 种写法:
https://www.jianshu.com/p/e17f82e8ebc9(此链接纯属骗阅读量,不信你点开看看)
2.1.2 工厂方法模式
定义:
定义一个用于创建对象的接口,让子类决定实例化哪个类。工厂方法使一个类的实例化延迟到其子类。
结构图:
角色:
- Product:抽象产品类。
- ConcreteProduct:具体产品类,实现 Product 接口。
- Factory:抽象工厂类,内部方法返回一个 Product 类型的对象。
- ConcreteFactory:具体工厂类,返回 ConcreteProduct 实例。
例:
(1)创建抽象工厂
public abstract class ComputerFactory {
public abstract T createComputer(Class clz);
}
(2)具体工厂
public class GDComputerFactory extends ComputerFactory {
@Override
public T createComputer(Class clz) {
Computer computer;
String className = clz.getName();
try {
computer = Class.forName(className).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return (T) computer;
}
}
2.1.3 创造者模式(生成器模式)
定义:
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
结构图:
角色:
- Director:导演类,负责安排已有模块的顺序,然后通知 Builder 开始建造。
- Builder:抽象 Builder 类,规范产品的组建,一般由子类实现。
- ConcreteBuilder:具体建造者,实现抽象 Builder 类定义的所有方法,并且返回一个组建好的对象。
- Product:产品类。
使用场景
- 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
- 相同的方法,不同的执行顺序,产生不同的事件结果时。
- 多个部件或零件都可以被装配到一个对象中,但是产生的运行结果又不相同时。
- 产品类非常复杂,或者产品类种的调用顺序不同产生了不同的效能。
- 在创建一些复杂的对象时,这些对象的内部组成构件间的建造顺序是稳定的,但是对象的内部组成构件面临着复杂的变化。
优点
- 使用建造者模式可以使客户端不必知道产品内部组成的细节。
- 具体的建造者类之间是相互独立的,容易扩展。
- 由于具体的建造者是独立的,因此可以对建造过程逐步细化,而不对其他的模块产生任何影响。
缺点
- 产生多余的 Builde 对象以及导演类。
2.2 结构型设计模式
结构型设计模式是从程序的结构上解决模块之间的耦合问题。
2.2.1 代理模式
定义:
为其他对象提供一种代理以控制对这个对象的访问。
结构图:
角色:
- Subject:抽象主题类,声明真是主题与代理的共同接口方法。
- RealSubject:真实主题类,代理类所代表的真实主题。客户端通过代理类间接地调用真实主题类的方法。
- Proxy:代理类,持有对真实主题类的引用,在其所实现的接口方法种调用真实主题类种相应的接口方法执行。
- Client:客户端类。
静态代理
(1)抽象主题类
public interface IShopping {
void buy();
}
(2)真实主题类
public class Xiaoming implements IShopping {
@Override
public void buy() {
System.out.println("小明买东西");
}
}
(3)代理类
public class Purchasing implements IShopping {
private IShopping mShopping;
public Purchasing(IShopping shopping) {
mShopping = shopping;
}
@Override
public void buy() {
mShopping.buy();
}
}
(4)客户端类
public class Client {
public static void main(String[] args) {
IShopping xiaoming = new Xiaoming();
IShopping purchasing = new Purchasing(xiaoming);
purchasing.buy();
}
}
动态代理
在代码运行时通过反射来动态地生成代理类对象,并确定到底来代理谁。
public class DynamicPurchasing implements InvocationHandler {
private Object obj;
public DynamicPurchasing(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(obj, args);
if("buy".equals(method.getName())) {
System.out.println("买东西");
}
return result;
}
}
public class Client {
public static void main(String[] args) {
IShopping xiaoming = new Xiaoming();
DynamicPurchasing dynamicPurchasing = new DynamicPurchasing(xiaoming);
IShopping purchasing = (IShopping) Proxy.newProxyInstance(xiaoming.getClass.getClassLoader(),
new Class[]{IShopping.class}, dynamicPurchasing);
purchasing.buy();
}
}
代理模式的类型
代理模式从编码的角度来说可以分为静态代理和动态代理,而从使用范围来讲则可分为以下 4 种类型:
- 远程代理:为一个对象在不同的地址空间提供局部代表,这样系统可以将 Server 部分的实现隐藏。
- 虚拟代理:使用一个代理对象表示一个十分耗费资源的对象,并在真正需要时才创建。
- 安全代理:用来控制真实对象访问时的权限。一般用于真实对象有不同的访问权限时。
- 智能指引:当调用真实的对象时,代理处理另外一些事,比如计算真实对象的引用计数,当该对象没有引用时,可以自动释放它;或者访问一个实际对象时,检查是否已经能够锁定它,以确保其他对象不能改变它。
优点
- 真实主题类就是实现实际的业务逻辑,不用关心其他非本职的工作。
- 真实主题类随时都会发生变化,但是因为它实现了公共的接口,所有代理类可以不做任何修改就能够使用。
2.2.2 装饰模式
定义
动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。
结构图
角色
- Component:抽象组件,可以是接口或者抽象类,被装饰的最原始的对象。
- ConcreteComponent:组件具体实现类。Component 的具体实现类,被装饰的具体对象。
- Decorator:抽象装饰着,从外类来拓展 Component 类的功能,但对于 Component 来说无须知道 Decorator 的存在。在它的属性种必然有一个 private 变量指向 Component 抽象组件。
- ConcreteDecorator:装饰者的具体实现类。
实现
杨过本身就会全真剑法,又有两位前辈洪七公和欧阳锋向杨过传授打狗棒法和蛤蟆功,这样杨过除了会全真剑法,还会打狗棒法和蛤蟆功。洪七公和欧阳锋就起到了“装饰”杨过的作用。
(1)抽象组件
public abstract class Swordman {
public abstract void attackMagic();
}
(2)组件具体实现类
public class YangGuo extends Swordman {
@Override
public void attackMagic() {
System.out.println("杨过使用全真剑法");
}
}
(3)抽象装饰者
public abstract class Master extends Swordman {
private Swordman mSwordman;
public Master(Swordman swordman) {
this.mSwordman = swordman;
}
@Override
public void attackMagic() {
mSwordman.attackMagic();
}
}
(4)装饰者具体实现类
public class HongQigong extends Master {
public HongQigong(Swordman swordman) {
super(swordman);
}
public void teachAttackMagin() {
System.out.println("洪七公教授打狗棒法");
System.out.println("杨过使用打狗棒法");
}
@Override
public void attackMagic() {
super.attackMagic();
teachAttackMagin();
}
}
public class OuYangFeng extends Master {
public OuYangFeng(Swordman swordman) {
super(swordman);
}
public void teachAttackMagin() {
System.out.println("欧阳锋教授蛤蟆功");
System.out.println("杨过使用蛤蟆功");
}
@Override
public void attackMagic() {
super.attackMagic();
teachAttackMagin();
}
}
(5)客户端调用
public class Client {
public static void main(String[] args) {
YangGuo yangguo = new YangGuo();
HongQigong hongqigong = new HongQigong(yangguo);
hongqigong.attackMagic();
OuYangFeng ouyangfeng = new OuYangFeng(yangguo);
ouyangfeng.attackMagic();
}
}
使用场景
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
- 需要动态地给一个对象增加功能,这些功能可以动态地撤销。
- 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。
优点
- 通过组合而非继承的方式,动态地扩展一个对象的功能,在运行时选择不同的装饰器,从而实现不同的行为。
- 有效避免了使用继承的方式扩展对象功能而带来的灵活性差、子类无限制扩张的问题。
- 具体组件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体组件类和具体装饰类,在使用时再对其进行组合,原有代码无需改变,符合“开放封闭原则”。
缺点
- 因为所有对象均继承自 Component,所以如果 Component 内部结构发生改变,则不可避免地影响所有子类(装饰者和被装饰者)。如果基类改变,则势必影响对象的内部。
- 比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难。对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为繁琐。所以,只在必要时使用装饰模式。
- 装饰层数不能过多,否则会影响效率。
2.2.3 外观模式(门面模式)
定义
要求一个子系统的外部与内部的通信必须通过一个统一的对象进行。此模式提供一个高层的接口,使得子系统更易于使用。
结构图
角色
- Facade:外观类,知道哪些子系统类负责处理请求,将客户端的请求代理给适当的子系统对象。
- Subsystem:子系统类,可以有一个或者多个子系统。实现子系统的功能,处理外观类指派的任务,注意子系统不含有外观类的引用。
实现
(1)子系统类
/**
* 子系统招式
*/
public class ZhaoShi {
public void TaiJiQuan() {
System.out.println("使用招式太极拳");
}
public void QiShangQuan() {
System.out.println("使用招式七伤拳");
}
public void ShengHuo() {
System.out.println("使用招式圣火令");
}
}
/**
* 子系统内功
*/
public class NeiGong {
public void JiuYang() {
System.out.println("使用九阳神功");
}
public void QianKun() {
System.out.println("使用乾坤大挪移");
}
}
/**
* 子系统经脉
*/
public class JingMai {
public void jingmai() {
System.out.println("开启经脉");
}
}
(2)外观类
/**
* 外观类张无忌
*/
public class ZhangWuJi {
private JingMai jingmai;
private ZhaoShi zhaoshi;
private NeiGong neigong;
public ZhangWuJi() {
jingmai = new JingMai();
zhaoshi = new ZhaoShi();
neigong = new NeiGong();
}
public void QianKun() {
jingmai.jingmai();
neigong.QianKun();
}
public void QiShang() {
jingmai.jingmai();
neigong.JiuYang();
zhaoshi.QiShangQuan();
}
}
(3)客户端调用
public class Client {
public static void main(String[] args) {
ZhangWuJi zhangwuji = new ZhangWuJi();
zhangwuji.QianKun();
zhangwuji.QiShang();
}
}
使用场景
- 构建一个有层次结构的子系统时,使用外观模式定义子系统中每层的入口点。如果子系统之间是相互依赖的,则可以让其通过外观接口进行通信,减少子系统之间的依赖关系。
- 子系统往往会因为不断地重构演化而变得越来越复杂,大多数的模式使用时也会产生很多很小的类,这给外部调用它们的用户程序带来了使用上的困难。我们可以使用外观类提供一个简单的接口,对外隐藏子系统的具体实现并隔离变化。
- 当维护一个遗留的大型系统时,可能这个系统已经非常难以维护和扩展,但因为它含有重要的功能,所以新的需求必须依赖它。这时可以使用外观类,为设计粗糙或者复杂的遗留代码提供一个简单的接口,让新系统和外观类交互,而外观类负责与遗留的代码进行交互。
优点
- 减少系统的相互依赖,所有的依赖都是对外观类的依赖,与子系统无关。
- 对用户隐藏了子系统的具体实现,减少用户对子系统的耦合。这样即使具体的子系统发生了变化,用户也不会感知到。
- 加强了安全性,子系统种的方法如果不在外观类种开通,就无法访问到子系统种的方法。
缺点
- 不符合开放封闭原则。如果业务出现变更,则可能要直接修改外观类。
2.2.4 享元模式
定义
使用共享对象有效地支持大量细粒度的对象。
结构图
角色
- Flyweight:抽象享元角色,同时定义出对象的外部状态和内部状态的接口或者实现。
- ConcreteFlyweight:具体享元角色,实现抽象享元角色定义的业务。
- FlyweightFactory:享元工厂,负责管理对象池和创建享元对象。
实现
“双11“促销中的商品信息。
(1)抽象享元角色
public interface IGoods {
public void showGoodsPrice(String name);
}
(2)具体享元角色
public class Goods implements IGoods {
private String name;
private String version;
public Goods(String name) {
this.name = name;
}
@Override
public void showGoodsPrice(String version) {
if("32G".equals(version)) {
System.out.println("价格为5199元");
} else if("128G".equals(version)) {
System.out.println("价格为5999元");
}
}
}
(3)享元工厂
public class GoodsFactory {
private static Map pool = new HashMap();
public static Goods getGoods(String name) {
if(pool.containsKey(name)) {
System.out.println("使用缓存,key 为:" + name);
return pool.get(name);
} else {
Goods goods = new Goods(name);
pool.put(name, goods);
System.out.println("创建商品,key 为:" + name);
return goods;
}
}
}
(4)客户端调用
public class Client {
public static void main(String[] args) {
Goods goods1 = GoodsFactory.getGoods("iPhone7");
goods.showGoodsPrice("32G");
Goods goods2 = GoodsFactory.getGoods("iPhone7");
goods.showGoodsPrice("128G");
}
}
使用场景
- 系统种存在大量的相似对象。
- 需要缓冲池的场景。
优点
- 享元模式是池技术的重要实现方式,它可以减少应用程序创建的对象数量,降低程序内存的占用,提高程序的性能。
2.3 行为型设计模式
行为性模式主要处理类或对象如何交互以及如何分配职责。
2.3.1 策略模式
定义
定义一系列的算法,把每一个算法封装起来,并且使它们可相互替换。策略模式使得算法可独立于使用它的客户而独立变化。
结构图
角色
- Context:上下文角色,用来操作策略的上下文环境,起到承上启下的作用,屏蔽高层模块对策略、算法的直接访问。
- Strategy:抽象策略角色,策略、算法的抽象,通常为接口。
- ConcreteStrategy:具体的策略实现。
实现
/**
* 战斗策略
*/
public interface FightingStrategy {
public void fighting();
}
(2)具体策略实现
public class WeakRivalStrategy implements FightingStrategy {
@Override
public void fighting() {
System.out.println("遇到了较弱的对手,张无忌使用太极剑");
}
}
public class CommonRivalStrategy implements FightingStrategy {
@Override
public void fighting() {
System.out.println("遇到了普通的对手,张无忌使用圣火令神功");
}
}
public class StrongRivalStrategy implements FightingStrategy {
@Override
public void fighting() {
System.out.println("遇到了强大的对手,张无忌使用乾坤大挪移");
}
}
(3)上下文角色
public class Context {
private FightingStrategy fightingStrategy;
public Context(FightingStrategy fightingStrategy) {
this.fightingStrategy = fightingStrategy;
}
public void fighting() {
fightingStrategy.fighting();
}
}
(4)客户端调用
public class ZhangWuJi {
public static void main(String[] args) {
Context context = new Context(new WeakRivalStrategy());
context.fighting();
}
}
使用场景
- 对客户隐藏具体策略(算法)的实现细节,彼此完全独立。
- 针对同一类型问题的多种处理方式,仅仅是具体行为有差别时。
- 在一个类中定义了很多行为,而且这些行为在这个类里的操作以多个条件语句的形式出现,策略模式将相关的条件分支移入它们各自的 Strategy 类中,以替代这些条件语句。
优点
- 使用策略模式可以避免使用多重条件语句。多重条件语句不易维护,而且易出错。
- 易于扩展。当需要添加一个策略时,只需要实现接口就可以了。
缺点
- 每一个策略都是一个类,复用性小。如果策略过多,类的数量会增多。
- 上层模块必须知道有哪些策略,才能够使用这些策略,这与迪米特原则相违背。
2.3.2 模板方法模式
定义
定义一个操作中的算法框架,而将一些步骤延迟到子类中,使得子类不改变一个算法的结构即可定义算法的某些特定步骤。
结构图
角色
- AbstractClass:抽象类,定义了一套算法框架。
- ConcreteClass:具体实现类。
实现
(1)创建抽象类,定义算法框架
public abstract class AbstractSwordman {
public final void fighting() {
neigong();
meridian();
if(hasWeapons()) {
weapons();
}
moves();
hook();
}
protected void hook() {};
protected abstract void neigong();
protected abstract void weapons();
protected abstract void moves();
protected void merdian() {
System.out.println("开启正经与奇经");
}
protected boolean hasWeapons() {
return true;
}
}
(2)具体实现类
public class ZhangWuJi extends AbstractSwordman {
@Override
protected void neigong() {
System.out.println("运行九阳神功");
}
@Override
protected void weapons() {}
@Override
protected void moves() {
System.out.println("使用乾坤大挪移");
}
@Override
protected boolean hasWeapons() {
return false;
}
}
(3)客户端调用
public class Client {
public static void main(String[] args) {
ZhangWuJi zhangwuji = new ZhangWuJi();
zhangwuji.fighting();
}
}
使用场景
- 多个子类有共有的方法,并且逻辑基本相同时。
- 面对重要、复杂的算法,可以把核心算法设计为模板方法,周边相关细节功能则由各个子类实现。
- 需要通过子类来决定父类算法种的某个步骤是否执行,实现子类对父类的反向控制。
优点
- 模板方法模式通过把不变的行为搬移到超类,去除了子类中重复代码。
- 子类实现算法的某些细节,有助于算法的扩展。
缺点
- 每个不同的实现都需要定义一个子类,这会导致类的个数的增加,设计更加抽象。
2.3.3 观察者模式(发布-订阅模式)
定义
定义对象间一种一对多的依赖关系,每当一个对象改变状态时,则所有依赖于它的对象都会得到通知并被自动更新。
结构图
角色
- Subject:抽象主题(抽象被观察者)。抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者。抽象主题提供接口,可以添加或者删除观察者对象。
- ConcreteSubject:具体主题(具体被观察者)。该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
- Observer:抽象观察者,是观察者的抽象类。它定义了一个更新接口,使得在得到主题更改通知时更新自己。
- ConcreteObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。
实现
微信某个公众号的更新通知。
(1)抽象观察者
public interface Observer {
public void update(String message);
}
(2)具体观察者
public class WeixinUser implements Observer {
private String name;
public WeixinUser(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println(name + "-" + message);
}
}
(3)抽象被观察者
public interface Subject {
public void attach(Observer observer);
public void detach(Observer observer);
public void notify(String message);
}
(4)具体被观察者
public class SubscriptionSubject implements Subject {
private List weixinUserList = new ArrayList();
@Override
public void attach(Observer observer) {
weixinUserList.add(observer);
}
@Override
public void detach(Observer observer) {
weixinUserList.remove(observer);
}
@Override
public void notify(String message) {
for(Observer observer : weixinUserList) {
observer.update(message);
}
}
}
(5)客户端调用
public class Client {
public static void main(String[] args) {
SubscriptionSubject subject = new SubscriptionSubject();
WeixinUser user1 = new WeixinUser("xiaoming");
WeixinUser user2 = new WeixinUser("xiaohua");
subject.attach(user1);
subject.attach(user2);
subject.notify("专栏更新");
}
}
使用场景
- 关联行为场景。需要注意的是,关联行为是可拆分的,而不是“组合”关系。
- 事件多级触发场景。
- 跨系统的消息交换场景,如消息队列、事件总线的处理机制。
优点
- 观察者和被观察者之间是抽象耦合,容易扩展。
- 方便建立一套触发机制。
缺点
- 在应用观察者模式时需要考虑一下开发效率和运行效率的问题。程序中包括一个被观察者、多个观察者,开发、调试内容比较复杂,而且在 Java 中消息的通知一般是顺序执行的,那么一个观察者卡顿,会影响整体的执行效率,在这种情况下,一般会采用异步方式。