设计模式之结构型

代理模式

代理模式之前已经讲过,附上链接代理模式

装饰者模式

装饰者模式定义:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。

装饰模式博主在第一次学习是懵逼的,是因为代理模式中代理对象和目标对象都实现了同一个接口,装饰者模式中装饰对象和被装饰对象也都实现了同一个接口,并且都可以很容易的在方法前面或后面增加一些功能来达到增强方法的目的。其实装饰模式就是代理模式的一个特殊应用,两者的共同点是都实现了同一个接口,不同点就是代理模式着重对代理过程的控制,而装饰模式是对类的功能进行增强或减弱,它着重类的功能变化。
java的规范必然是jdk源码了,学习装饰者模式一定要看下io的实现。io是负责读取数据的,而数据的来源又分为文件、字节数组、字符缓冲区、线程输入流、序列化的对象等等。如下图所示:
《设计模式之结构型》
图有点丑….输入流读取数据按照数据来源已经分成五类,按理来说应该够用了,那为什么还会有BufferedInputStream,DataInputStream,PushbackInputStream呢?我们拿bufferinputstream来分析,因为io流读取是访问硬盘,硬盘的访问速度在计算机的世界里一定是极慢的,也是极其耗资源的,为了解决这个问题,发明了BufferedInputStream,先读取buffer,实在找不到再去硬盘里找。那我们怎么将其加入到io家族中呢?如果我们给每种io流增加子类,使其拥有读取buffer的能力,那么仅以图中展示出来的,就要加5个子类,加上DataInputStream(byte转成java数据类型),PushbackInputStream(读取之后写回流)那就是15个子类,已经形成了类爆炸的问题,任谁看到都是头大的…
加入我们需要一个既可以读buffer又可以将数据转成java类型的输入流,正确的姿势是这样的:

//InputStream inputStream = new BufferedInputStream(new DataInputStream(new FileInputStream("")));
BufferedInputStream inputStream = new BufferedInputStream(new DataInputStream(new FileInputStream("")));

从这里就可以看出来装饰者模式和依赖倒置原则是有点冲突的,因为依赖倒置原则让我们依赖抽象,说白了就是面向接口编程,如果我们面向接口使用InputStream声明,它依然无法拥有缓存的能力,这证明了想要找出一段完全符合六大设计模式的代码是很难的…

适配器模式

将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够一起工作。

适配器模式最好在设计阶段不要考虑它,他不是为了解决开发阶段存在的问题,而是解决已经上线的项目问题。任何一个架构师都不应该在设计阶段考虑使用适配器模式,并且项目一定要严格遵守依赖倒置原则和里氏替换原则,适配器模式才会带给你巨大的好处,否则即使在适合用适配器模式的情况下,也会给项目带来巨大改造。
现在假设这样一个场景,A公司收购了B公司,要对B公司的用户数据和项目进行一个内部消化,但两个系统的实现又不一样,如何最小的改动最优的整合到一起呢?我们拿用户模块来看下,A公司的用户是这样设计的:

public interface IUserInfo {
    String getName();
    String getSex();
    void getMobile();
}
public class UserInfo implements IUserInfo {

    @Override
    public String getName() {
        System.out.println("老子是光头强");
    }

    @Override
    public String getSex() {
        System.out.println("老子是男性");
    }
    @Override
    public String getMobile() {
        System.out.println("手机号:12345678");
    }
}

B公司用户模块是这样设计的:

public interface IUserDetail {
    Map getUserBaseinfo();
}
public class UserDetail implements IUserDetail {

    private Map map;

    @Override
    public Map getUserBaseinfo() {
        map = new HashMap();
        map.put("name","张三");
        map.put("sex","男");
        map.put("phone","1896369448");
        return null;
    }
}

首先我们的系统要和他们的系统进行交互,不可能为了这一小小的功能对我们已经良好运行的项目进行大改动,那怎么办呢?只能转化了,拿到对方的数据对象然后转化为我们自己的数据对象。设计是这样子的:

public class NewUser extends UserDetail implements IUserInfo  {
    @Override
    public String getName() {
        String name = (String) super.getUserBaseinfo().get("name");
        System.out.println(name);
        return name;
    }

    @Override
    public String getSex() {
        return (String) super.getUserBaseinfo().get("sex");
    }

    @Override
    public String getMobile() {
        return (String) super.getUserBaseinfo().get("mobile");
    }
}

测试类是这样的:

//大boss获取A公司人员的手机号,临时反悔想要B公司人员的手机号
public class Client {

   // public static void main(String[] args) {
        //IUserInfo userInfo = new UserInfo();
        //userInfo.getMobile();  
    //}
    
    public static void main(String[] args) {
       //临时改变主意获取B公司人员的手机号
        IUserInfo userInfo1 = new NewUser();
        userInfo1.getMobile();
    }
    
}

使用适配器模式只修改了一句话,其他的业务逻辑不用修改就既觉了对接的问题。适配器可以让两个没有任何关系的类在一起运行,只要适配器这个角色能搞定他们就行。适用适配器模式的场景只要记住一点就够了:当你要去修改一个已经投入到生产当中的接口,适配器模式可能是最适合你的模式了。适配器模式不应在设计阶段考虑,它是为了解决已经上线的问题的存在。

门面模式

要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。

门面模式注重统一的对象,提供一个访问子系统的接口,除了这个接口不允许有任何访问子系统的行为发生。门面对象是外界访问子系统内部的唯一通道,不管子系统内部多么杂乱无章,只要门面对象在,就可以做到”金玉其外,败絮其中”。
源码中应用此模式的非常多,slf4j使用的就是此模式。这是一个简单的设计模式,直接给大家上例子:

public interface Drink {
    void drink();
}
public class Milk implements Drink {
    @Override
    public void drink() {
        System.out.println("喝牛奶");
    }
}
public class RedTea implements Drink {
    @Override
    public void drink() {
        System.out.println("喝红茶");
    }
}
public class Water implements Drink {
    @Override
    public void drink() {
        System.out.println("喝凉白开");
    }
}
public class Facade {

    private Milk milk = new Milk();
    private RedTea redTea = new RedTea();
    private Water water = new Water();

    public void drinkMilk(){
        milk.drink();
    }
    public void drinkRedTea(){
        redTea.drink();
    }
    public void drinkWater(){
        water.drink();
    }
}

统一的对象指的就是门面,也就是上述例子中的facade。
门面模式可以减少系统间的互相依赖,因为所有的依赖都是依赖于门面对象,与子系统的业务无关。只依赖门面对象,也就是说子系统的业务如何变化只要不影响到门面对象,大大提高了灵活性。缺点就是过于依赖门面对象,不符合开闭原则,一旦门面对象中出现错误,对子系统的打击是非常大的,修改门面对象时,一定要慎之又慎。

组合模式

将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。

概念有点抽象,但其实很简单。相信大部分猿都用过,只是不知道他还有这个名字罢了。简单的模式不啰嗦,直接上例子:

public class Person {
    private String name;
    private String age;

    private List<Person> list = new ArrayList<>();

    public void addPerson(){
        list.add(new Person());
    }
    public void removePerson(Person person){
        list.remove(person);
    }
    public Person getChild(int i){
        return list.get(i);
    }
}

每个人都有年龄和姓名,每个人也都会有孩子。构造成树形结构,使单个对象和组合对象的访问具有一致性。模式很简单,不浪费大家时间。

享元模式

使用共享对象可有效的支持大量的细粒度的对象。

享元模式是池技术实现的核心思想。其核心概念有共享对象和细粒度的对象.细粒度的对象的特点就是对象数量多切性质相近,我们应将其不变的部分抽出来形成共享对象,存放在池中,减少内存使用.复用对象最简单的方式是,用一个 HashMap 来存放每次新生成的对象。每次需要一个对象的时候,先到 HashMap 中看看有没有,如果没有,再生成新的对象,然后将这个对象放入 HashMap 中.代码很简单,不在举例.

桥梁模式

将抽象和实现解耦,使得两者可以独立的变化。

桥梁模式的关键点在于解耦。现在的公司有很多,我们今天拿互联网公司和外包公司来举例。每个公司会有很多部门,每个部门工作的方式又不一样,模式很简单,直接上例子:

//抽象部门接口,用来定义要实现的方法
public interface IDepartment {
     //每个部门都要工作,公司请你不是来吃闲饭的
     void work();
}
//苦逼的程序员
public class Coder implements IDepartment {
    @Override
    public void work() {
        System.out.println("程序猿工作:将咖啡变成代码");
    }
}
//苦逼的测试
public class QA implements IDepartment {
    @Override
    public void work() {
        System.out.println("测试:保证产品质量");
    }
}
//it公司
public abstract class ITCompany {
    //引入部门抽象
    private IDepartment iDepartment;

    public ITCompany(IDepartment iDepartment) {
        this.iDepartment = iDepartment;
    }
    public void work(){
        iDepartment.work();
    }
}
//互联网公司
class InternetCompany extends ITCompany{

    public InternetCompany(IDepartment iDepartment) {
        super(iDepartment);
    }

}
//外包公司
class outerPackCompany extends ITCompany{

    public outerPackCompany(IDepartment iDepartment) {
        super(iDepartment);
    }
}
public class Client {
    public static void main(String[] args) {
        ITCompany itCompany = new InternetCompany(new Coder());
        itCompany.work();
        ITCompany outerPackCompany = new outerPackCompany(new QA());
        outerPackCompany.work();
    }
}

怎么样,很简单把。它的重点就是解耦,符合开闭原则和依赖倒置原则。在此架构的基础上,我们不管是想要增加抽象还是具体实现类都是易如反掌。桥梁模式的意图就是对变化的封装,把可能变化的封装到最小的逻辑单元,避免风险扩散.

总结

设计模式到此就算告一段落了。学习设计模式可以有助于我们写出优雅、易维护、易扩展的代码,也可以让我们更好的理解源码。为了写设计模式的文章,看了两三本关于设计模式的书,翻阅了许多资料,讲的不是特别好,但总归可以带大家入门。若是大家想看书学习,按照难度从低到高可自行选择:<大话设计模式>、<设计模式之禅>、gof的<设计模式>.

    原文作者:设计模式
    原文地址: https://segmentfault.com/a/1190000013036653
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞