浅谈23种设计模式(4/23)

设计模式汇总

创建型模式

这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用新的运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。

  1. 工厂模式(Factory Pattern)
  2. 抽象工厂模式(Abstract Factory Pattern)
  3. 单例模式(Singleton Pattern)
  4. 建造者模式(Builder Pattern)
  5. 原型模式(Prototype Pattern)

结构型模式

这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。

  1. 适配器模式(Adapter Pattern)
  2. 桥接模式(Bridge Pattern)
  3. 过滤器模式(Filter、Criteria Pattern)
  4. 组合模式(Composite Pattern)
  5. 装饰器模式(Decorator Pattern)
  6. 外观模式(Facade Pattern)
  7. 享元模式(Flyweight Pattern)
  8. 代理模式(Proxy Pattern)

行为型模式

这些设计模式特别关注对象之间的通信。

  1. 责任链模式(Chain of Responsibility Pattern)
  2. 命令模式(Command Pattern)
  3. 解释器模式(Interpreter Pattern)
  4. 迭代器模式(Iterator Pattern)
  5. 中介者模式(Mediator Pattern)
  6. 备忘录模式(Memento Pattern)
  7. 观察者模式(Observer Pattern)
  8. 状态模式(State Pattern)
  9. 空对象模式(Null Object Pattern)
  10. 策略模式(Strategy Pattern)
  11. 模板模式(Template Pattern)
  12. 访问者模式(Visitor Pattern)

1 适配器模式

定义

将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作。
我们用图来形象的解释这一概念:

1.png
2.png

这里的重点在于,老接口有特殊功能,我们不忍舍弃,比如只有欧洲的插头(老接口)能从插座里获得电,没有电我们过不下去。这个功能我们无法舍弃
为了继续享受这个特殊功能,我们使用适配器模式,让我们手中的美国的插头(新接口),拥有老接口的能力。

本例使用场景

例如一个美国人说英语,一个中国人说中文,为了跟美国人做生意,说英文这个功能我们无法舍弃,但是我们是中国人,天生不会说英文。于是两者想要交流,就需要一个适配器,来充当沟通两者的工作。现在,我们希望让一个能说中国话的个体(实现说中文的接口的类),开口说英文。

角色

  • 目标接口(Target)(中文沟通能力的接口):定义了我们希望用来承载被适配类特殊功能的方法的接口,比如例子中,我们希望用中国人的说话方法,承载美国人的speak功能,说话方法就定义在该接口中。
  • 目标类(Target class)(中国人类):我们所期待的拥有特殊功能的具体类,也就是要说英文的类。
  • 需要适配的类(Adaptee)(美国人类):需要适配的类或适配者类,类中还有我们还不忍舍弃的功能,我们需要迂回实现他
  • 适配器(Adapter)(翻译类):通过包装一个需要适配的对象,把原接口转换成目标接口。

类适配器

适配器有两种主要的实现,我们先看第一种——类适配器

具体实现

// 被适配类,已存在的、具有还有用处的特殊功能、但不符合我们既有的标准接口的类 
//——本例中即为一个会说f**k的美国人(你可以看作这个美国人实现了说英文的接口,不过这个无关紧要,省略),他说的话我们听不懂
class American{  
    public void speak() {  
        System.out.println("f**k");  
    }  
}  
// 目标接口,或称为标准接口  ——这里是一个说中文能力的接口,他定义了方法“说话”。
interface SpeakChinese {  
    public void shuoHua();  
} 
// 具体目标类,只提供普通功能 ——这里我们的具体实现是一个中国人类,他实现了说中国话的接口
class Chinese implements SpeakChinese {  
    public void shuoHua() {  
        System.out.println("敲里吗");  
    }  
}  
// 适配器类,继承了被适配类,同时实现标准接口  ——现在我们觉得,那个美国人说的四字真言好拽哦,我也要学会,于是我们定义了适配器
class Adapter extends American implements SpeakChinese {  
    public void shuoHua() {  
        super.speak();  
    }  
}  

测试

现在我们定义一个laoWang,老王是一个Chinese,他会说话这个方法。
然后再定义一个适配器,看看适配器能不能用中文的说话方法,说出英文来。

// 测试类public class Client {  
    public static void main(String[] args) {  
        // 使用普通功能类  
        SpeakChinese laoWang= new Chinese();  
        laoWang.shuoHua();  
          
        // 使用特殊功能类,即适配类  
        SpeakChinese adapter = new Adapter();  
        adapter.shuoHua();  
    }  
}  

测试结果:

敲里吗
f**k

很棒,现在用了适配器,适配器用说中文的方式,说出了这句著名的英文,现在我们迂回得到了说这四个字母的能力,可以去找外国友人交流感情了。

对象适配器

另外一种适配器模式是对象适配器,它不是使用多继承或继承再实现的方式,而是使用直接关联,或者称为委托的方式。

具体实现

其他目标类和被适配类都一样,就是适配器类的定义方式有所不同:

// 适配器类,直接关联被适配类,同时实现标准接口  
class Adapter implements SpeakChinese {  
    // 直接关联被适配类  
    private American american;  
      
    // 可以通过构造函数传入具体需要适配的被适配类对象  
    public Adapter (American american) {  
        this.american = american;  
    }  
      
    public void shuoHua() {  
        // 这里是使用委托的方式完成特殊功能  
        this.american.speak();  
    }  
}  

测试

在这里,我们为了更灵活一点,定义了一个加州人,加州人和外面那些妖艳贱货不一样,他们比较优雅,一般喜欢说hello。

class Californian extends American{  
    public void speak() {  
        System.out.println("hello");  
    }  
} 
public class Client {  
    public static void main(String[] args) {  
        // 使用普通功能类  
        SpeakChinese laoWang = new Chinese();  
        laoWang.shuoHua();  
          
        // 使用特殊功能类,即适配类,  
        // 需要先创建一个被适配类的对象作为参数  
        American tom = new Californian(){}
        Target adapter = new Adapter(tom);  
        adapter.shuoHua();  
    }  
}  

测试结果

敲里吗
hello

同样的,我们用适配器获得了像加州人那样优雅的说英文的能力。对象适配器相对于类适配器来说比较灵活,我们可以复用这个适配器,通过传参的不同,得到德克萨斯人,弗吉尼亚人,佛罗里达人的说话方式。

优劣

模式总结

优点

  • 通过适配器,客户端可以调用同一接口,因而对客户端来说是透明的。这样做更简单、更直接、更紧凑。
  • 复用了现存的类,解决了现存类和复用环境要求不一致的问题。
  • 将目标类和适配者类解耦,通过引入一个适配器类重用现有的适配者类,而无需修改原有代码。
  • 一个对象适配器可以把多个不同的适配者类适配到同一个目标,也就是说,同一个适配器可以把适配者类和它的子类都适配到目标接口。

缺点

  • 过多的使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。

2 策略模式

定义

首先我们理解策略的概念,策略就是一组算法,一种实现。策略模式定义了一系列的算法,并将每一个算法封装起来,而且使他们可以相互替换,让算法独立于使用它的客户而独立变化。

本例使用场景

例如神机妙算的诸葛亮,他的每个锦囊,就是一个策略,现在诸葛亮给了关羽三个锦囊——锦囊A,锦囊B,锦囊C,告诉关羽如果敌军数量在一万到三万以内,打开锦囊A;敌军数量在三万到五万之间,打开锦囊B;还更多的话,打开锦囊C。

角色

  • 环境(Context)(具体策略的执行者——关羽):持有一个Strategy的引用。
  • 抽象策略(Strategy)(锦囊接口或者抽象类):这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
  • 具体策略(ConcreteStrategy)(具体的锦囊类):包装了相关的算法或行为。

具体实现

//抽象策略类 锦囊接口
public interface JinNang {
    /**
     * 策略方法 打开锦囊
     */
    public void openJinNang();
}
//具体策略类 锦囊A
public class JinNangA implements JinNang {

    @Override
    public void openJinNang(Integer enermyNum) {
        System.out.println("朝敌军使用抛掷粪便攻击,杀敌20%!伤敌数量为:"+enermyNum*0.2);
    }

}

//具体策略类 锦囊B
public class JinNangB  implements JinNang {

    @Override
    public void openJinNang(Integer enermyNum) {
        System.out.println("朝敌军播放难忘今宵以瓦解敌军意志,杀敌50%!伤敌数量为:"+enermyNum*0.5);
    }

}

//具体策略类 锦囊C
public class JinNangC implements JinNang {

    @Override
    public void openJinNang(Integer enermyNum) {
        System.out.println("丫的还不快跑!?");
    }

}
//环境角色类--关羽
public class GuanYu{
    //持有一个具体策略的对象
    private JinNang jinNang;

    /**
     * 策略方法
     */
    public void fight(Integer enermyNum){
        if (enermyNum >10000&enermyNum<30000){
            jinNang=new JinNangA();
        } else if (enermyNum >30000&enermyNum<50000){
            jinNang=new JinNangB();
        } else{
            jinNang=new JinNangC();
        }
        jinNang.openJinNang(enermyNum);
    }

}

测试

好的,现在我们的关羽跨上赤兔马,拎起青龙刀,来到了阵前,对面分别出动了两个师,四个师,十个师的兵力干他!即便如此,我们的小英雄也A了上去!

public static void main(String[] args) {
        GuanYu guanYu = new GuanYu();
        guanYu.fight(20000);
        guanYu.fight(40000);
        guanYu.fight(100000);
    }

测试结果
显而易见,测试结果是:

朝敌军使用抛掷粪便攻击,杀敌20%!伤敌数量为:4000.0
朝敌军播放难忘今宵以瓦解敌军意志,杀敌50%!伤敌数量为:20000.0
丫的还不快跑!?

模式总结

  • 策略模式的重心不是如何实现算法,而是如何组织、调用这些算法,从而让程序结构更灵活,具有更好的维护性和扩展性。
  • 运行期间,策略模式在每一个时刻只能使用一个具体的策略实现对象,虽然可以动态地在不同的策略实现中切换,但是同时只能使用一个。

优劣

优点

  • 策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码移到父类里面,从而避免代码重复。
  • 使用策略模式可以避免使用多重条件(if-else)语句。多重条件语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重条件语句里面,比使用继承的办法还要原始和落后。

缺点

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道算法或行为的情况。
  • 由于策略模式把每个具体的策略实现都单独封装成为类,如果备选的策略很多的话,那么对象的数目就会很可观。

3 外观模式

定义

外观模式(Facade Pattern)又称为门面模式,它为系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得子系统更加容易使用。该模式可以隐藏系统的复杂性,提供了客户端请求的简化方法和对现有系统类方法的委托调用。

本例使用场景

蜀汉集团总经理诸葛亮为了光复中原,推出了“三个臭皮匠”阵法,由关羽张飞赵云压阵,在砍人的时候充当蜀军的门面。于是在群P的时候,对于他们的客户(敌人)来说,和外观(阵法)交互(打架)并不需要知道他们内在的细节,许多繁琐的逻辑,已经被门面封装了。

角色

  • 门面(facade)角色(三个臭皮匠阵法):外观模式的核心。它被客户角色调用,它熟悉子系统的功能。内部根据客户角色的需求预定了几种功能的组合。
  • 子系统(Subsystem)角色(关张赵三个子系统):子系统类,实现子系统的功能,处理外观类指派的任务,注意子系统类不含有外观类的引用。

具体实现

我们先来定义三个子系统:关张赵

/**
 * 子系统关羽
 */
public class GuanYu {
    //剪刀
    public void jianDao(){
        System.out.println("关羽出剪刀");
    }
    public void shiTou(){
        System.out.println("关羽出石头");
    }
    public void bu(){
        System.out.println("关羽出布");
    }
}
/**
 * 子系统赵云
 */
public class ZhaoYun{
    //剪刀
    public void jianDao(){
        System.out.println("赵云出剪刀");
    }
    public void shiTou(){
        System.out.println("赵云出石头");
    }
    public void bu(){
        System.out.println("赵云出布");
    }
}
/**
 * 子系统张飞
 */
public class ZhangFei {
    //剪刀
    public void jianDao(){
        System.out.println("张飞出剪刀");
    }
    public void shiTou(){
        System.out.println("张飞出石头");
    }
    public void bu(){
        System.out.println("张飞出布");
    }
}

接下来定义统一的外观类——三个臭皮匠阵法,这个阵法有三个绝技,青龙腾,白龙腾和黑龙腾,至于技能内到底怎么实现的,你们这些凡人就不要在意了,总之发动起来,天上都是龙,你们只要负责喊666就可以了。

public class ThreeChouPiJiang{
    private GuanYu guanYu;
    private ZhangFei zhangFei;
    private ZhaoYun zhaoYun;
    ThreeChouPiJiang(){
        guanYu =new GuanYu();
        zhangFei = new ZhangFei();
        zhaoYun = new ZhaoYun();
    }
    //青龙腾
    public void qingLongTeng(){
        zhangFei.bu();
        zhaoYun.bu();
        guanYu.shiTou();
        System.out.println("青龙腾:关羽输了,关羽出去砍人;");
        System.out.println("BO~BO~BO~经费燃烧中~ =============");
    }
    //黑龙腾
    public void heiLongTeng(){
        guanYu.bu();
        zhaoYun.bu();
        zhangFei.shiTou();
        System.out.println("黑龙腾:张飞输了,张飞出去砍人;");
        System.out.println("BO~BO~BO~经费燃烧中~ =============");
    }
    //白龙腾
    public void baiLongTeng(){
        guanYu.bu();
        zhangFei.bu();
        zhaoYun.shiTou();
        System.out.println(":赵云输了,赵云出去砍人;");
        System.out.println("BO~BO~BO~经费燃烧中~ =============");
    }
}

测试

好了,我们的阵法已经定义好了,现在诸葛亮意气风发,决定要北伐中原,我们的三个臭皮匠阵法,开始发挥威力:

public static void main(String[] args) {
    ThreeChouPiJiang threeChouPiJiang = new ThreeChouPiJiang();
    ThreeChouPiJiang threeChouPiJiang = new ThreeChouPiJiang();
    threeChouPiJiang.baiLongTeng();
    threeChouPiJiang.qingLongTeng();
    threeChouPiJiang.heiLongTeng();
}

测试结果:

关羽出布
张飞出布
赵云出石头
白龙腾:赵云输了,赵云出去砍人;
BO~BO~BO~经费燃烧中~ =============
张飞出布
赵云出布
关羽出石头
青龙腾:关羽输了,关羽出去砍人;
BO~BO~BO~经费燃烧中~ =============
关羽出布
赵云出布
张飞出石头
黑龙腾:张飞输了,张飞出去砍人;
BO~BO~BO~经费燃烧中~ =============

威力果然是十分惊人啊!对于敌人而言,他们只知道三个臭皮匠阵法使用了绝招,根本不会知道绝招内部居然是这三个货用剪刀石头布搞出来的。在顶级特效的加持下,这个门面还是十分整洁十分威风十分唬人的。

模式总结

外观模式的目的不是给予子系统添加新的功能接口,而是为了让外部减少与子系统内多个模块的交互,松散耦合,从而让外部能够更简单地使用子系统。
外观模式的本质是:封装交互,简化调用。

优劣

优点

  • 松散耦合,使得客户端和子系统之间解耦,让子系统内部的模块功能更容易扩展和维护;
  • 简单易用,客户端根本不需要知道子系统内部的实现,或者根本不需要知道子系统内部的构成,它只需要跟Facade类交互即可。
  • 更好的划分访问层次,有些方法是对系统外的,有些方法是系统内部相互交互的使用的。子系统把那些暴露给外部的功能集中到门面中,这样就可以实现客户端的使用,很好的隐藏了子系统内部的细节。

缺点

  • 在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”

4 观察者模式

定义

在对象之间定义了一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象会收到通知并自动更新。
简单来说,其实就是发布订阅模式,发布者发布信息,订阅者获取信息,订阅了就能收到信息,没订阅就收不到信息。

本例使用场景

例如微信公众号服务,不定时发布一些消息,关注公众号就可以收到推送消息,取消关注就收不到推送消息。

角色

  • 抽象被观察者(公众号接口):也就是一个抽象主题,它把所有对观察者对象的引用保存在一个集合中,每个主题都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者角色。一般用一个抽象类和接口来实现。
  • 抽象观察者(订阅人接口):为所有的具体观察者定义一个接口,在得到主题通知时更新自己。
  • 具体被观察者(公众号):也就是一个具体的主题,在集体主题的内部状态改变时,所有登记过的观察者发出通知。
  • 具体观察者(订阅人):实现抽象观察者角色所需要的更新接口,一边使本身的状态与制图的状态相协调。

具体实现

/***
 * 抽象被观察者接口
 * 声明了添加、删除、通知观察者方法
 */
public interface Observerable {
    
    public void registerObserver(Observer o);//新增订阅人
    public void removeObserver(Observer o);//删除订阅人
    public void notifyObserver();//发布消息
    
}
/***
 * 抽象观察者
 * 定义了一个update()方法,当被观察者调用notifyObservers()方法时,观察者的update()方法会被回调。
 */
public interface Observer {
    public void update(String message);//更新消息
}
/**
 * 被观察者,也就是微信公众号服务
 * 实现了Observerable接口,对Observerable接口的三个方法进行了具体实现
 */
public class WechatServer implements Observerable {
    //注意到这个List集合的泛型参数为Observer接口,设计原则:面向接口编程而不是面向实现编程
    private List<Observer> list;
    private String message;
    
    public WechatServer() {
        list = new ArrayList<Observer>();
    }
    
    @Override
    public void registerObserver(Observer o) {
        
        list.add(o);
    }
    
    @Override
    public void removeObserver(Observer o) {
        if(!list.isEmpty())
            list.remove(o);
    }
    
    //遍历通知
    @Override
    public void notifyObserver() {
        for(int i = 0; i < list.size(); i++) {
            Observer oserver = list.get(i);
            oserver.update(message);
        }
    }
    
    public void setInfomation(String s) {
        this.message = s;
        System.out.println("微信服务更新消息: " + s);
        //消息更新,通知所有观察者
        notifyObserver();
    }
}
/**
 * 观察者
 * 实现了update方法
 */
public class User implements Observer {

    private String name;
    private String message;
    
    public User(String name) {
        this.name = name;
    }
    
    @Override
    public void update(String message) {
        this.message = message;
        read();
    }
    
    public void read() {
        System.out.println(name + " 收到推送消息: " + message);
    }
    
}

测试

首先注册了三个用户,ZhangSan、LiSi、WangWu。公众号发布了一条消息"PHP是世界上最好用的语言!",三个用户都收到了消息。

用户ZhangSan看到消息后颇为震惊,果断取消订阅,这时公众号又推送了一条消息,此时用户ZhangSan已经收不到消息,其他用户

还是正常能收到推送消息。

public class Test {
    
    public static void main(String[] args) {
        WechatServer server = new WechatServer();
        
        Observer userZhang = new User("ZhangSan");
        Observer userLi = new User("LiSi");
        Observer userWang = new User("WangWu");
        
        server.registerObserver(userZhang);
        server.registerObserver(userLi);
        server.registerObserver(userWang);
        server.setInfomation("PHP是世界上最好用的语言!");
        
        System.out.println("----------------------------------------------");
        server.removeObserver(userZhang);
        server.setInfomation("JAVA是世界上最好用的语言!");
        
    }
}

测试结果:
[图片上传失败...(image-ef81d4-1524021404702)]

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342