此文写给想要理解设计模式,但认为《设计模式》中所举的例子过于复杂的读者。
前人的经验告诉我们:使用以下设计模式提升软件的可维护性:
创建型模式:将实例化过程抽象化
1.1 工厂方法
1.2 抽象工厂
1.3 建造者模式
结构型模式:通过组合类和对象来获得更大的结构
2.1 桥接模式
2.2 代理模式
2.3 组合模式
行为模式:涉及到算法和对象间职责的分配
3.1 观察者模式
3.2 访问者模式
3.3 备忘录模式
3.4 状态模式
1.1 工厂模式
工厂模式的原则就是:绝对不要让用户直接使用new操作来新建对象。
我们现在有产品接口、两个产品类:
public interface Fruit { void whatIm(); }
public class Apple implements Fruit { @Override public void whatIm() { //苹果 } }
public class Pear implements Fruit { @Override public void whatIm() { //梨 } }
先看看简单工厂:
public class FruitFactory { public Fruit createFruit(String type) { if (type.equals("apple")) {//生产苹果 return new Apple(); } else if (type.equals("pear")) {//生产梨 return new Pear(); } return null; } }
它根据传入参数的不同生产不同的产品。但是如果有新增加的产品,就要修改工厂类了。有些人觉得这不妥,并且认为,每种产品需要有一个工厂。
于是我们需要把两种产品的new操作包装起来,写一个工厂接口,两个工厂对象:
public interface FruitFactory { Fruit createFruit();//生产水果 }
public class AppleFactory implements FruitFactory { @Override public Fruit createFruit() { return new Apple(); } }
public class PearFactory implements FruitFactory { @Override public Fruit createFruit() { return new Pear(); } }
这就是工厂方法了。
用户用如下方法使用:
AppleFactory appleFactory = new AppleFactory(); PearFactory pearFactory = new PearFactory(); Apple apple = (Apple) appleFactory.createFruit();//获得苹果 Pear pear = (Pear) pearFactory.createFruit();//获得梨
1.2 抽象工厂模式
现在我们有两种产品:CPU、屏幕
CPU有两种:850、650
屏幕有两种:大、小
所以我们一共有六个类:CPU接口、850CPU、650CPU、屏幕接口、大屏幕、小屏幕
现在写出抽象工厂接口、红米手机零件抽象工厂、小米手机零件抽象工厂
小米手机零件抽象工厂包含了850CPU工厂、大屏幕工厂的功能
红米手机零件抽象工厂包含了650CPU工厂、小屏幕工厂的功能
总结一下,抽象工厂就是把两种不同的工厂方法拼在一起。
1.3 建造者模式
我们的程序要模拟雷军的手机生产过程。
雷军的手机分为红米和小米,虽然里面的配件不一样,但是壳子都是一样的,所以不需要不同的类来表示。以下是表示Mi系列手机的类:
/** * Mi系列手机 */ public class MiPhone { private String screen; private String cpu; /** * 安装屏幕 * @param screen 将要安装的屏幕 */ public void setScreen(String screen) { this.screen = screen; } /** * 安装CPU * @param cpu 将要安装的CPU */ public void setCpu(String cpu) { this.cpu = cpu; } }
可以看到,可以使用set操作给手机装屏幕和CPU。但这个工作肯定不能交给用户做,所以我们需要用一个Builder类来完成手机制造和装配。
这个类相当于一台生产小米或者红米手机的机器。
先写一个抽象类来完成生产小米手机和红米手机都需要的步骤:制作外壳、交付手机
public abstract class MiPhoneBuilder { protected MiPhone phone; /** * 制作一个手机外壳 */ public void BuildAPhone() { this.phone = new MiPhone(); } /** * 交付,返回装配好零件的手机 * @return 制作好的手机 */ public MiPhone getPhone() { return this.phone; } /** * 给手机装配零件 */ public abstract void installScreen(); public abstract void installCpu(); }
不同种手机需要装配不同的零件,这样的工作分别交给MiPhoneBuilder的不同的子类来做。装配零件的方法其实就是把产品类的Setter包装一下。
/** * 生产小米手机 */ public class XiaomiBuilder extends MiPhoneBuilder { @Override public void installScreen() { this.phone.setScreen("5英寸大屏"); } @Override public void installCpu() { this.phone.setCpu("高通骁龙830"); } }
/** * 生产红米手机 */ public class RedmiBuilder extends MiPhoneBuilder { @Override public void installScreen() { this.phone.setScreen("4英寸小屏"); } @Override public void installCpu() { this.phone.setCpu("高通骁龙650"); } }
然后,写一个BuildManager类,它相当于手机的生产商。将Builder与它组合,并用新方法将MiPhoneBuilder类的生产外壳和组装配件的方法连接起来,再包装一下getPhone方法,这样就能根据传入参数(相当于老板的命令)的不同来决定生产哪种手机了。
/** * 手机生产商 */ public class BuildManager { MiPhoneBuilder builder; /** * 构造方法:确定该生产商的生产机器能够生产哪种手机 */ public BuildManager(MiPhoneBuilder builder) { this.builder = builder; } /** * 生产一台手机 */ public void buildNewPhone() { builder.BuildAPhone(); builder.installCpu(); builder.installScreen(); } /** * 返回生产的手机 * @return */ public MiPhone getPhone() { return builder.getPhone(); } }
用户需要这样使用:
public static void main(String[] args) { BuildManager manager = new BuildManager(new RedmiBuilder()); manager.buildNewPhone(); MiPhone phone = manager.getPhone(); }
2.1 桥接模式
我们的程序需要模拟Mi手机的装配。Mi手机的外壳都是相同的,但具有不同的CPU。
/** * 抽象类:Mi手机 */ public abstract class MiPhone { protected Cpu cpu; protected String name; /** * 构造方法:安装CPU * @param cpu */ public MiPhone(Cpu cpu) { this.cpu = cpu; } public String getName() { return this.name; } }
注意到MiPhone类组合了一个Cpu类的对象。
这个抽象类有两个具体子类:XiaomiPhone和RedmiPhone
Cpu是一个接口,有两种实现:Cpu830和Cpu650
用户使用时,可以将XiaomiPhone, RedmiPhone和两种Cpu任意组合,比如:
public static void main(String[] args) { MiPhone xiaomiIV = new XiaomiPhone(new Cpu830()); MiPhone redmiVI = new RedmiPhone(new Cpu650()); MiPhone fakeXiaomi = new XiaomiPhone(new Cpu650()); MiPhone fakeRedmi = new RedmiPhone(new Cpu830()); }
2.2 代理模式
它更像一个提升性能的设计模式,使用了Lazy思想。
因为比较简单所以就直接用语言叙述了。
我们已有的东西是一个类和它的接口。这个类的构造方法里有一些需要耗费大量时间代价的东西,将会极大地影响性能。于是我们对这个接口新写一个实现类,这个新类中有一个对原类的引用。这个新类里面几乎什么都没有,所以创建它的时间代价很小。当用户不需要使用原类的方法时,就用这个新类“占位”就行(比如说塞到列表或集合里);当用户需要使用原类的方法时,这个新类创建一个原类的对象,然后将操作委派给原类的对象。
2.3 组合模式
在某个类的实例中,包含一个同类对象的集合,组成树形结构。
3.1 观察者模式
我们有一个万众瞩目的对象,我们称它为被观察者
它有很多观察它的人,我们称它们为观察者
使用方法:
3.2 访问者模式
https://www.jianshu.com/p/0fdf3470ff68
总结一下:某一个ADT拥有一个成员变量:列表,其中的元素包含某类的两个子类的对象。这个ADT有一个操作Action,它遍历列表元素,对每一个对象都干一些事情。但是我们希望这个操作Action能够干各种各样的事情,于是我们给这个ADT组合一个Visitor对象(Visitor是接口名),在Visitor的不同实现类中实现我们希望Action能干的各种各样的事情,然后将Action的操作委派给Visitor。调用Action时,传入对应的Visitor,就能进行对应的操作了。
这样的好处就是,新增操作时不用修改原来的代码,只需要新建一个Visitor实现类。
3.3 备忘录模式
本例中,我们的程序要模拟用着前去挑战恶龙的场景。
我们已经拥有一个勇者ADT:
public class Hero { int hp; int mp; String weapon; public Hero() { hp = 100; mp = 78; weapon = "天使的祝福之刃"; } /** * 受伤:hp减少 */ public void getHurt() { hp -= 15; } /** * 死亡:hp归零 */ public void getKilled() { hp = 0; } /** * 使用技能:mp减少 */ public void useSkill() { mp -= 15; } /** * 意外:武器被偷走了 */ public void weaponStolen() { weapon = null; } }
可以看到有很多操作可以改变勇者的状态。勇者一旦挑战失败,游戏就结束了。我们当然不想要从头开始游戏,所以在挑战恶龙之前我们需要存档,即保存勇者的当前状态。
1. 设计Memento类
Memento用来保存勇者的某个状态的信息。勇者的状态由hp, mp, weapon的取值决定,于是我们设计的Memento也需要有这三个成员变量。
/** * 勇者的备忘录 */ public class HeroMemento { final int hp; final int mp; final String weapon; /** * 构造方法:根据勇者的状态信息新建一个备忘录 */ public HeroMemento(int hp, int mp, String weapon) { this.hp = hp; this.mp = mp; this.weapon = weapon; } /** * 从备忘录读取存档时的hp */ public int getHp() { return this.hp; } /** * 从备忘录读取存档时的mp */ public int getMp() { return this.mp; } /** * 从备忘录读取存档时的weapon */ public String getWeapon() { return this.weapon; } }
2. 新建MementoManager类来管理备忘录
这个有一个备忘录的列表,来管理各个存档点
/** * 存档管理器 */ public class MementoManager { List<HeroMemento> saveData; // AF // 保存各个存档点 /** * 构造方法 */ public MementoManager() { saveData = new ArrayList<>(); } /** * 新增一条存档 * @param memento */ public void add(HeroMemento memento) { saveData.add(memento); } /** * 读取特定编号的存档 */ public HeroMemento get(int index) { return saveData.get(index); } }
3. 将一个MementoManager对象组合到Hero中,然后给Hero添加save, recover方法
public class Hero { int hp; int mp; String weapon; MementoManager saveData = new MementoManager();
/** * 保存当前状态到存档列表末位 */ public void save() { saveData.add(new HeroMemento(this.hp, this.mp, this.weapon)); } /** * 恢复到指定存档的状态 * @param index 指定存档的编号 */ public void restore(int index) { HeroMemento memento = this.saveData.get(index); this.hp = memento.getHp(); this.mp = memento.getMp(); this.weapon = memento.getWeapon(); }
这样就ok了。
在模拟程序中,这样使用:
public static void main(String[] args) { Hero hero = new Hero(); hero.save(); hero.challengeDragon(); if (hero.die()) hero.restore(0); }
3.4 状态模式
某个ADT具有多个操作和多个状态。如果该ADT在不同“状态”时,各个操作的内容都不同,这个时候就可以使用状态模式。
编写状态接口、不同的状态的实现类,然后将ADT在这些状态下的操作写在各个状态实现类中。
举个栗子:现在我们需要编写程序来模拟大猪头在高兴的时候、不高兴的时候会做的事情。
1. 设计大猪头ADT
/** * 大猪头 */ public class BigPig { // 两个操作:吃、睡 public void eat(); public void sleep(); }
因为在不同心情状态下,吃、睡的内容不一样,所以我们将操作交给状态类来完成。
2. 编写状态接口
/** * 心情状态接口 * 因为要设计的大猪头ADT只会做两个操作,所以我们每个心情只需要实现 * 吃、睡 * 两个操作 */ public interface MoodState { /** * 吃 */ public void eat(); /** * 睡 */ public void sleep(); }
3. 实现两种心情
/** * 高兴的心情状态 */ public class Happy implements MoodState { @Override public void eat() { System.out.println("健康饮食"); } @Override public void sleep() { System.out.println("早睡早起"); } }
/** * 不高兴的心情状态 */ public class Sad implements MoodState { @Override public void eat() { System.out.println("暴饮暴食"); } @Override public void sleep() { System.out.println("睡懒觉"); } }
4. 将ADT的操作委派给MoodState
/** * 大猪头 */ public class BigPig { MoodState state; /** * 构造方法:设置初始心情状态 * @param state 初始心情状态 */ public BigPig(MoodState state) { this.state = state; } /** * Setter: 改变心情状态 * @param state 要变成的心情状态 */ public void setState(MoodState state) { this.state = state; } // 两个操作:吃、睡 public void eat() { state.eat(); } public void sleep() { state.sleep(); } }
最后,在模拟程序中这样使用它:
public static void main(String[] args) { BigPig bigPig = new BigPig(new Happy()); // 初始状态:好心情 bigPig.eat(); bigPig.sleep(); bigPig.setState(new Sad()); // 改变状态:坏心情 bigPig.eat(); bigPig.sleep(); }
举例就到此结束了。因为尽量选取了方便理解的例子,所以可能过于简单,请多多包涵。
/** * 不高兴的心情状态 */public class Sad implements MoodState {
@Override public void eat() { System.out.println(“暴饮暴食”); }
@Override public void sleep() { System.out.println(“睡懒觉”); }
}
转载于:https://www.cnblogs.com/snt165/p/11074899.html