前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >快速梳理23种常用的设计模式(下篇)

快速梳理23种常用的设计模式(下篇)

作者头像
Rude3Knife的公众号
发布2019-08-07 11:32:45
4120
发布2019-08-07 11:32:45
举报
文章被收录于专栏:后端技术漫谈后端技术漫谈

前言

本文旨在快速梳理常用的设计模式,了解每个模式主要针对的是哪些情况以及其基础特征,每个模式前都有列举出一个或多个可以深入阅读的参考网页,以供读者详细了解其实现。

分为三篇文章:

  • 上篇:设计模式基础理念和创建型设计模式
  • 中篇:行为型设计模式
  • 下篇:结构型设计模式

面试知识点复习手册

通过以下两种途径查看全复习手册文章导航

  • 关注我的公众号:Rude3Knife 点击公众号下方:技术推文——面试冲刺
  • 全复习手册文章导航(CSDN)

快速回忆

结构型

  • 适配器(Adapter)
  • 装饰器(Decorator)
  • 代理模式(Proxy)
  • 外观模式/门面模式(Facade)
  • 桥接模式(Bridge Pattern)
  • 组合模式(Composite)
  • 享元模式(Flyweight)

理念

首先搞清楚一点,设计模式不是高深技术,不是奇淫技巧。设计模式只是一种设计思想,针对不同的业务场景,用不同的方式去设计代码结构,其最最本质的目的是为了解耦,延伸一点的话,还有为了可扩展性和健壮性,但是这都是建立在解耦的基础之上。

高内聚低耦合

高内聚:系统中A、B两个模块进行交互,如果修改了A模块,不影响模块B的工作,那么认为A有足够的内聚。

低耦合:就是A模块与B模块存在依赖关系,那么当B发生改变时,A模块仍然可以正常工作,那么就认为A与B是低耦合的。

结构型

适配器(Adapter)

https://www.jianshu.com/p/93821721bf08

定义

客户类调用适配器的方法时,在适配器类的内部将调用适配者类的方法,而这个过程对客户类是透明的,客户类并不直接访问适配者类。因此,适配器可以使由于接口不兼容而不能交互的类可以一起工作。这就是适配器模式的模式动机。

在这里插入图片描述

角色

  • 目标(Target)角色:这就是所期待得到的接口。注意:由于这里讨论的是类适配器模式,因此目标不可以是类。
  • 源(Adapee)角色:现在需要适配的接口。
  • 适配器(Adaper)角色:适配器类是本模式的核心。适配器把源接口转换成目标接口。显然,这一角色不可以是接口,而必须是具体类。

类适配器

创建新类,继承源类,并实现新接口

代码语言:javascript
复制
class  adapter extends oldClass  implements newFunc{}

对象适配器

创建新类持源类的实例,并实现新接口

代码语言:javascript
复制
class adapter implements newFunc { private oldClass oldInstance ;}
  • 类适配器使用对象继承的方式,是静态的定义方式
  • 而对象适配器使用对象组合的方式,是动态组合的方式。

接口适配器

创建新的抽象类实现旧接口方法

代码语言:javascript
复制
abstract class adapter implements oldClassFunc { void newFunc();}

总结

建议尽量使用对象适配器的实现方式,多用合成/聚合、少用继承。当然,具体问题具体分析,根据需要来选用实现方式,最适合的才是最好的。

优点
  • 更好的复用性
  • 更好的扩展性
缺点

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

装饰模式(Decorator)

给一类对象增加新的功能,装饰方法与具体的内部逻辑无关。

实现

设计不同种类的饮料,饮料可以添加配料,比如可以添加牛奶,并且支持动态添加新配料。每增加一种配料,该饮料的价格就会增加,要求计算一种饮料的价格。

下图表示在 DarkRoast 饮料上新增新添加 Mocha 配料,之后又添加了 Whip 配料。DarkRoast 被 Mocha 包裹,Mocha 又被 Whip 包裹。它们都继承自相同父类,都有 cost() 方法,外层类的 cost() 方法调用了内层类的 cost() 方法。

在这里插入图片描述

代码

代码语言:javascript
复制
interface Source{ void method();}
public class Decorator implements Source{

    private Source source ;
    public void decotate1(){
        System.out.println("decorate");
    }
    @Override
    public void method() {
        decotate1();
        source.method();
    }
}

装饰模式与代理模式的区别

装饰器模式关注于在一个对象上动态的添加方法,然而代理模式关注于控制对对象的访问。

  • 用代理模式,代理类(proxy class)可以对它的客户隐藏一个对象的具体信息。因此,当使用代理模式的时候,我们常常在一个代理类中创建一个对象的实例。
  • 当我们使用装饰器模 式的时候,我们通常的做法是将原始对象作为一个参数传给装饰者的构造器。

代理模式(Proxy)

详细代码实例:https://www.cnblogs.com/daniels/p/8242592.html

简介

代理模式的定义:代理模式给某一个对象提供一个代理对象并由代理对象控制对原对象的引用。

通俗的来讲代理模式就是我们生活中常见的中介

为什么要用代理模式

  • 中介隔离作用 在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
  • 开闭原则,增加功能 真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。

有哪几种代理模式

静态代理

由程序员创建或特定工具自动生成源代码,在对其编译。在程序员运行之前,代理类.class文件就已经被创建了。

代码:静态代理创建代理类

代码语言:javascript
复制
package main.java.proxy.impl;

import main.java.proxy.BuyHouse;

public class BuyHouseProxy implements BuyHouse {

    private BuyHouse buyHouse;

    public BuyHouseProxy(final BuyHouse buyHouse) {
        this.buyHouse = buyHouse;
    }

    @Override
    public void buyHosue() {
        System.out.println("买房前准备");
        buyHouse.buyHosue();
        System.out.println("买房后装修");

    }
}

静态代理总结

  • 优点:可以做到在符合开闭原则的情况下对目标对象进行功能扩展。
  • 缺点:我们得为每一个服务都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。
动态代理:JDK反射机制(接口代理)
  • 是在程序运行时通过反射机制动态创建的。
  • 为需要拦截的接口生成代理对象以实现接口方法拦截功能。

代码:编写动态处理器

代码语言:javascript
复制
package main.java.proxy.impl;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class DynamicProxyHandler implements InvocationHandler {

    private Object object;

    public DynamicProxyHandler(final Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("买房前准备");
        Object result = method.invoke(object, args);
        System.out.println("买房后装修");
        return result;
    }
}

代码:编写测试类

代码语言:javascript
复制
package main.java.proxy.test;

import main.java.proxy.BuyHouse;
import main.java.proxy.impl.BuyHouseImpl;
import main.java.proxy.impl.DynamicProxyHandler;

import java.lang.reflect.Proxy;


public class DynamicProxyTest {
    public static void main(String[] args) {
        BuyHouse buyHouse = new BuyHouseImpl();
        BuyHouse proxyBuyHouse = (BuyHouse) Proxy.newProxyInstance(BuyHouse.class.getClassLoader(), new
                Class[]{BuyHouse.class}, new DynamicProxyHandler(buyHouse));
        proxyBuyHouse.buyHosue();
    }
}

动态代理总结

  • 优势:虽然相对于静态代理,动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度。
  • 劣势:只能对接口进行代理
动态代理:CGLIB代理
  • 其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑
  • 但因为采用的是继承,所以不能对final修饰的类进行代理
  • JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。

代码见网页

CGLIB代理总结:(与JDK代理区别:)

CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多

  • 所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。
  • 同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。

外观模式/门面模式(Facade)

https://www.cnblogs.com/lthIU/p/5860607.html

在这里插入图片描述

在这里插入图片描述

简单来说,该模式就是把一些复杂的流程封装成一个接口供给外部用户更简单的使用。这个模式中,涉及到3个角色。

角色

1)门面角色:外观模式的核心。它被客户角色调用,它熟悉子系统的功能。内部根据客户角色的需求预定了几种功能的组合。

2)子系统角色: 实现了子系统的功能。它对客户角色和Facade时未知的。它内部可以有系统内的相互交互,也可以由供外界调用的接口。

3)客户角色: 通过调用Facede来完成要实现的功能。

代码

代码语言:javascript
复制
package com.huawei.facadeDesign.facade;

import org.apache.log4j.Logger;

import com.huawei.facadeDesign.children.CPU;
import com.huawei.facadeDesign.children.Disk;
import com.huawei.facadeDesign.children.Memory;


/**
 * 门面类(核心)
 * @author Administrator
 *
 */
public class Computer
{
    public static final Logger LOGGER = Logger.getLogger(Computer.class);

    private CPU cpu;
    private Memory memory;
    private Disk disk;
    public Computer()
    {
        cpu = new CPU();
        memory = new Memory();
        disk = new Disk();
    }
    public void start()
    {
        LOGGER.info("Computer start begin");
        cpu.start();
        disk.start();
        memory.start();
        LOGGER.info("Computer start end");
    }

    public void shutDown()
    {
        LOGGER.info("Computer shutDown begin");
        cpu.shutDown();
        disk.shutDown();
        memory.shutDown();
        LOGGER.info("Computer shutDown end...");
    }
}

优点

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

桥接模式(Bridge Pattern)

http://www.cnblogs.com/houleixx/archive/2008/02/23/1078877.html

含义

在软件系统中,某些类型由于自身的逻辑,它具有两个或多个维度的变化,那么如何应对这种“多维度的变化”?

如何利用面向对象的技术来使得该类型能够轻松的沿着多个方向进行变化,而又不引入额外的复杂度?这就要使用Bridge模式。

在这里插入图片描述

由上图变为下图

在这里插入图片描述

代码

详细代码见参考网页

代码语言:javascript
复制
static void Main(string[] args){
    //男人开着公共汽车在高速公路上行驶;
    Console.WriteLine("=========================");
    AbstractRoad Road3 = new SpeedWay();
    Road3.Car = new Bus();
    people p = new Man();
    p.Road = Road3;
    p.Run();
    Console.Read();
}

组合模式(Composite)

https://www.cnblogs.com/lfxiao/p/6816026.html

组合模式是为了表示那些层次结构,同时部分和整体也可能是一样的结构,常见的如文件夹或者树.

定义

组合模式定义了如何将容器对象和叶子对象进行递归组合,使得客户在使用的过程中无须进行区分,可以对他们进行一致的处理。

在使用组合模式中需要注意一点也是组合模式最关键的地方:叶子对象和组合对象实现相同的接口。这就是组合模式能够将叶子节点和对象节点进行一致处理的原因。

角色

1.Component :组合中的对象声明接口,在适当的情况下,实现所有类共有接口的默认行为。声明一个接口用于访问和管理Component子部件。

2.Leaf:叶子对象。叶子结点没有子结点。

3.Composite:容器对象,定义有枝节点行为,用来存储子部件,在Component接口中实现与子部件有关操作,如增加(add)和删除(remove)等。

适用场景

1、需要表示一个对象整体或部分层次,在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,可以一致地对待它们。

2、让客户能够忽略不同对象层次的变化,客户端可以针对抽象构件编程,无须关心对象层次结构的细节。

享元模式(Flyweight)

https://www.cnblogs.com/chenssy/p/3330555.html

定义

所谓享元模式就是运行共享技术有效地支持大量细粒度对象的复用,所以享元模式要求能够共享的对象必须是细粒度对象。

  • 内部状态:在享元对象内部不随外界环境改变而改变的共享部分。
  • 外部状态:随着环境的改变而改变,不能够共享的状态就是外部状态。

代码

享元工厂类FlyweightFactory:

利用了HashMap保存已经创建的颜色

代码语言:javascript
复制
public class FlyweightFactory{
    static Map<String, Shape> shapes = new HashMap<String, Shape>();

    public static Shape getShape(String key){
        Shape shape = shapes.get(key);
        //如果shape==null,表示不存在,则新建,并且保持到共享池中
        if(shape == null){
            shape = new Circle(key);
            shapes.put(key, shape);
        }
        return shape;
    }

    public static int getSum(){
        return shapes.size();
    }
}

客户端程序:Client.java

代码语言:javascript
复制
public class Client {
    public static void main(String[] args) {
        Shape shape1 = FlyweightFactory.getShape("红色");
        shape1.draw();

        Shape shape2 = FlyweightFactory.getShape("灰色");
        shape2.draw();

        Shape shape3 = FlyweightFactory.getShape("绿色");
        shape3.draw();

        Shape shape4 = FlyweightFactory.getShape("红色");
        shape4.draw();

        Shape shape5 = FlyweightFactory.getShape("灰色");
        shape5.draw();

        Shape shape6 = FlyweightFactory.getShape("灰色");
        shape6.draw();

        System.out.println("一共绘制了"+FlyweightFactory.getSum()+"中颜色的圆形");
    }
}

参考

简书大牛:https://www.jianshu.com/nb/10186551

Github:https://github.com/CyC2018/Interview-Notebook/blob/master/notes/设计模式.md

菜鸟网:http://www.runoob.com/design-pattern/design-pattern-tutorial.html

补充:

23种设计模式总结:

https://www.cnblogs.com/tongkey/p/7170826.html

https://www.cnblogs.com/malihe/p/6891920.html

-----正文结束-----

更多精彩文章,请查阅我的博客或关注我的公众号:Rude3Knife

全复习手册文章导航

  • 关注我的公众号:Rude3Knife 点击公众号下方:技术推文——面试冲刺
  • 全复习手册文章导航(CSDN)

知识点复习手册文章推荐

关注我

我是蛮三刀把刀,目前为后台开发工程师。主要关注后台开发,网络安全,Python爬虫等技术。

来微信和我聊聊:yangzd1102

Github:https://github.com/qqxx6661

原创博客主要内容

  • 笔试面试复习知识点手册
  • Leetcode算法题解析(前150题)
  • 剑指offer算法题解析
  • Python爬虫相关技术分析和实战
  • 后台开发相关技术分析和实战

同步更新以下博客

1. Csdn

http://blog.csdn.net/qqxx6661

拥有专栏:Leetcode题解(Java/Python)、Python爬虫开发、面试助攻手册

2. 知乎

https://www.zhihu.com/people/yang-zhen-dong-1/

拥有专栏:码农面试助攻手册

3. 掘金

https://juejin.im/user/5b48015ce51d45191462ba55

4. 简书

https://www.jianshu.com/u/b5f225ca2376

个人公众号:Rude3Knife

如果文章对你有帮助,不妨收藏起来并转发给您的朋友们~

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-03-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 后端技术漫谈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 面试知识点复习手册
  • 快速回忆
  • 理念
    • 高内聚低耦合
    • 结构型
      • 适配器(Adapter)
        • 定义
        • 角色
        • 类适配器
        • 对象适配器
        • 接口适配器
        • 总结
      • 装饰模式(Decorator)
        • 实现
        • 代码
        • 装饰模式与代理模式的区别
      • 代理模式(Proxy)
        • 简介
        • 为什么要用代理模式
        • 有哪几种代理模式
      • 外观模式/门面模式(Facade)
        • 角色
        • 代码
        • 优点
      • 桥接模式(Bridge Pattern)
        • 含义
        • 代码
      • 组合模式(Composite)
        • 定义
        • 角色
        • 适用场景
      • 享元模式(Flyweight)
        • 定义
        • 代码
        • 原创博客主要内容
        • 个人公众号:Rude3Knife
    • 参考
    • -----正文结束-----
    • 关注我
    相关产品与服务
    容器服务
    腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档