学习笔记-Java设计模式-结构型模式3

Java设计原则&&模式学习笔记

说明

近期扫地生决定整合一下年初学习的设计模式,一来用于复习巩固,二来也希望可以把自己的整合与有需要的同学共勉。

扫地生在学习的过程中主要参考的是公众号“一角钱技术”的相关推文,同时也参考了下列几篇文章。对这些作者扫地生表示衷心的感谢。

参考文章1-Java设计原则

参考文章2-Java设计模式总结

参考文章3-23种设计模式速记

4、结构型模式

4.5 结构型模式5——享元模式(Flyweight)

速记关键词:汉字编码

简介

定义:运用共享技术有效地支持大量细粒度的对象。

享元模式要求细粒度对象,那么就会使得对象数量多且性质相近,因此我们将对象信息分为两部分:内部状态(intrinsic)和外部状态(extrinsic):

  • 内部状态:对象共享出来的信息,存储在享元对象并且不会随环境改变而改变;
  • 外部状态:是对象得以依赖的一个标记,是随环境的改变而改变的,不可以共享的状态;

在一个系统中对象会使得内存占用过多,特别是那些大量重复的对象,这就是对系统资源的极大浪费。享元模式对对象的重用提供了一种解决方案,它使用共享技术对相同或者相似对象实现重用。享元模式就是运行共享技术有效地支持大量细粒度对象的复用。系统使用少量对象,而且这些都比较相似,状态变化小,可以实现对象的多次复用。这里有一点要注意:享元模式要求能够共享的对象必须是细粒度对象。享元模式通过共享技术使得系统中的对象个数大大减少了,同时享元模式使用了内部状态和外部状态,同时外部状态相对独立,不会影响到内部状态,所以享元模式能够使得享元对象在不同的环境下被共享。同时正是分为了内部状态和外部状态,享元模式会使得系统变得更加复杂,同时也会导致读取外部状态所消耗的时间过长。

图片

解决的问题

享元模式能够解决重复对象内存浪费问题,当系统中有大量相似对象,需要缓冲池时。不需要总是创建对象,可以从缓冲池中拿。这样可以降低系统内存,同时提高效率。

模式组成

组成(角色) 作用
Flyweight 抽象享元类
ConcreteFlyweight 具体享元类
UNsharedConcreteFlyweight 非共享具体享元类
FlyweightFactory 享元工厂类

实例

package top.saodisheng.designpattern.flyweight;

import java.util.HashMap;

/**
 * description:
 *
 * @author 扫地生_saodisheng
 * @date 2021-02-05
 */
public class FlyWeightPattern {
    public static void main(String[] args) {

        Flyweight flyweight1 = FlyweightFactory.getFlyweight("1");
        Flyweight flyweight2 = FlyweightFactory.getFlyweight("1");
        Flyweight flyweight3 = FlyweightFactory.getFlyweight("2");

    }
}

/**
 * 1. 抽象享元类,定义一个对象的内部状态和外部状态或实现的抽象类
 */
abstract class Flyweight {

    /** 内部状态 **/
    private String intrinsic;
    /** 外部状态 **/
    protected  final String Extrinsic;

    /**
     * 要求享元角色必须接受外部状态
     * @param Extrinsic
     */
    public Flyweight(String Extrinsic){
        this.Extrinsic = Extrinsic;
        System.out.println(" Extrinsic: "+ Extrinsic +" created. ");
    }

    /**
     * 定义业务操作
     */
    public abstract void operate();

    /**
     * 内部状态的getter/setter
     * @return
     */
    public String getIntrinsic() {
        return intrinsic;
    }

    public void setIntrinsic(String intrinsic) {
        this.intrinsic = intrinsic;
    }
}

/**
 * 2. 定义具体享元类,定义一个具体的产品类,需要注意的是内部状态处理应该与环境无关,
 * 不应该出现一个操作改变了外部状态,同时也改变了内部状态
 */
class ConcreteFlyweight extends Flyweight {

    /** 接受外部状态 **/
    public ConcreteFlyweight(String Extrinsic){
        super(Extrinsic);
    }

    /**
     * 根据外部状态进行逻辑处理
     */
    @Override
    public void operate() {
        //业务逻辑
    }
}

/**
 * 3. 创建享元工厂,构造一个池容器,提供从池中获取对象的方法
 */
class FlyweightFactory {
    /** 定义一个池容器 **/
    private static HashMap<String, Flyweight> pool=new HashMap<String, Flyweight>();
    /** 享元工厂 **/
    public static Flyweight getFlyweight(String Extrinsic){
        //需要返回的对象
        Flyweight flyweight = null;
        //如果在池中有该对象,直接从池中拿
        if (pool.containsKey(Extrinsic)){
            flyweight = pool.get(Extrinsic);
        }else {
            //如果池中没有该对象,则创建一个并放入池中
            flyweight = new ConcreteFlyweight(Extrinsic);
            pool.put(Extrinsic,flyweight);
        }
        return flyweight;
    }
}

image-20211107192141827

通过结果可知:外部状态为1的只创建了一次。

优缺点

优点:如果系统有大量类似的对象,可以节省大量的内存及CPU资源。

缺点:增加了系统的复杂性,需要分离出外部状态和内部状态,并且外部状态具有固定属性,不应该随内部状态改变而改变。

应用场景

常用于系统底层开发,以便解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在这些连接对象中有我们需要的则直接拿来用,避免重新创建。如果没有我们需要的,则创建一个。

在一个系统中有大量相似对象,需要缓冲池的场景。不需要一直创建一个新的对象,可以直接从缓冲池里拿。这样可以降低系统内存,同时提高效率。

  • 系统中存在大量的相似对象;
  • 细粒度的对象都具备较接近的外部状态,而且内部状态与环境无关;
  • 需要缓冲池的场景

源码中的应用

#JDK
String,Integer,Long...
com.sun.org.apache.bcel.internal.generic.InstructionConstants
......

4.6 结构型模式6——门面模式(外观模式,Facade)

速记关键词:对外统一接口

简介

定义:为子系统中的一组接口提供一个一致的接口,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

通过创建一个统一的外观类,用来包装子系统中一个 / 多个复杂的类,客户端可通过调用外观类的方法来调用内部子系统中所有方法。

图片

解决的问题

  • 避免了系统与系统之间的高耦合度
  • 使得复杂的子系统用法变得简单

实例

package top.saodisheng.designpattern.facade;

/**
 * description:
 *
 * @author 扫地生_saodisheng
 * @date 2021-02-06
 */
public class FacadePattern {
    public static void main(String[] args) {
        Facade facade = new Facade();
        facade.MethodA();
        facade.MethodB();
        System.out.println("----over----");
    }
}

/**
 * 1. 定义子系统
 */
class SubSystemOne {
    public void methodOne() {
        System.out.println("子系统方法一");
    }
}

class SubSystemTwo {
    public void methodTwo() {
        System.out.println("子系统方法二");
    }
}

class SubSystemThree {
    public void methodThree() {
        System.out.println("子系统方法三");
    }
}

class SubSystemFour {
    public void methodFour() {
        System.out.println("子系统方法四");
    }
}

/**
 * 2. 定义外观类
 */
class Facade {
    SubSystemOne one;
    SubSystemTwo two;
    SubSystemThree three;
    SubSystemFour four;

    public Facade() {
        this.one = new SubSystemOne();
        this.two = new SubSystemTwo();
        this.three = new SubSystemThree();
        this.four = new SubSystemFour();
    }

    public void MethodA() {
        System.out.println("方法组A()----");
        one.methodOne();
        two.methodTwo();
        four.methodFour();
    }

    public void MethodB() {
        System.out.println("方法组B()----");
        two.methodTwo();
        three.methodThree();
    }
}

image-20211107193522546

优缺点

优点:

  • 减少系统的相互依赖
  • 外观模式通过封装子系统,向上层模块提供统一的接口,从而降低了上层模块与子系统之间的过度耦合
  • 提高了灵活性
  • 提高安全性

缺点:不符合开闭原则——在不对外观进行抽象的时候,如果需要添加新的子系统,就需要对Facade进行修改

应用场景

  1. 要为一个复杂的子系统对外提供一个简单的接口

  2. 提供子系统的独立性

  3. 客户程序与多个子系统之间存在很大的依赖性

  4. 在层次化结构中,可以使用外观模式定义系统中每一层的入口

与适配器模式的区别

外观模式的实现核心主要是:由外观类去保存各个子系统的引用,实现由一个统一的外观类去包装多个子系统类,然而客户端只需要引用这个外观类,然后由外观类来调用各个子系统中的方法。

源码中的应用

#tomcat
org.apache.catalina.connector.RequestFacade
org.apache.catalina.connector.ResponseFacade
#mybatis
Configuration
......

4.7 结构型模式7——桥接模式(Bridge)

速记关键词:继承树拆分

简介

定义:将抽象部分和它的实现部分分离,使他们都可以独立地变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。

如果说某个系统能够从多个角度来进行分类,且每一种分类都可能会变化,那么我们需要做的就是将这多个角度分离出来,使得他们能独立变化,减少他们之间的耦合,这个分离过程就使用了桥接模式。所谓桥接模式就是讲抽象部分和实现部分隔离开来,使得他们能够独立变化。桥接模式将继承关系转化成关联关系,封装了变化,完成了解耦,减少了系统中类的数量,也减少了代码量。

img

模式实现

package top.saodisheng.designpattern.bridge.v1;

/**
 * description:
 * 桥接模式
 *
 * @author 扫地生_saodisheng
 * @date 2021-02-05
 */
public class BridgePattern {
    public static void main(String[] args) {
        Implementor imple = new ConcreteImplementorA();
        Abstraction abs = new RefinedAbstraction(imple);
        abs.Operation();
    }
}

/**
 * 1. 定义实现化接口
 */
interface Implementor {
    void OperationImpl();
}

/**
 * 2. 定义具体实现化类
 */
class ConcreteImplementorA implements Implementor {

    @Override
    public void OperationImpl() {
        System.out.println("具体实现化类被访问");
    }
}

/**
 * 3. 定义抽象化类
 */
abstract class Abstraction {
    protected Implementor imple;

    protected Abstraction(Implementor imple) {
        this.imple = imple;
    }
    public abstract void Operation();
}

/**
 * 4. 扩展抽象化类
 */
class RefinedAbstraction extends Abstraction {

    protected RefinedAbstraction(Implementor imple) {
        super(imple);
    }

    @Override
    public void Operation() {
        System.out.println("扩展抽象化类被访问");
        imple.OperationImpl();
    }
}

image-20211107195832213

解决的问题

在有多种可能变化的情况下,用继承会造成爆炸问题,扩展起来不灵活

模式组成

可以将抽象化部分与实现化部分分开,取消二者的继承关系,改用组合关系。

组成(角色) 作用
抽象化(Abstraction)角色 定义抽象类,并包含一个对实现化对象的引用
扩展抽象化(Refined Abstraction)角色 是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法
实现化(Implementor)角色 定义实现化角色的接口,供扩展抽象化角色调用
具体实现化(Concrete Implementor)角色 给出实现化角色接口的具体实现

实例说明

某公司开发了一个财务管理系统,其中有个报表生成器的工具模块,客户可以指定任意一种报表类型,如基本报表,往来报表,资金报表,资产报表等,并且可以指定不同 的报表样式,如饼图,柱状图等。系统设计人员针对这个报表生成器的结构设计了如下图所示的类图。

图片

后来在客户使用过程中,客户又希望增加一个新的报表和新的线形图,开发人员这个时候发现维护起来非常麻烦,设计人员经过仔细分析,发现存在严重的问题,因为新增加一个报表或者图,需要增加很多子类。所以,系统分析师最终对这个模块根据面向对象的设计原则对上面的方案进行了重构,重构后的图如下所示。

图片

在本重构方案中,将报表和图形设计成两个继承结构,两者都可以独立变化,编程的时候可以只针对抽象类编码,而在运行的时候再将具体的图形子类对象注入到具体的 报表类中。这样的话,系统就具有良好的可扩展性和可维护性,并且满足了面向对象设计原则的开闭原则。

使用步骤

package top.saodisheng.designpattern.bridge.v2;

/**
 * description:
 * 桥接模式
 *
 * @author 扫地生_saodisheng
 * @date 2021-02-05
 */
public class BridgePattern {
    public static void main(String[] args) {
        //实现化和抽象化分离

        // 基本报表
        IReport basicReport = new BasicReport();
        // 往来报表
        IReport intercourseReport = new IntercourseReport();
        // 资金报表
        IReport capitalReport = new CapitalReport();

        // 基本报表使用柱状图
        AbstractionGraph barchart = new Barchart(basicReport);
        barchart.operation();

        // 基本报表使用饼图
        AbstractionGraph piechart = new Piechart(basicReport);
        piechart.operation();
    }
}

/**
 * 1 定义一个实现化角色,报表接口
 */
interface IReport {
    void operationImpl();
}

/**
 * 2 定义具体实现化角色(基本报表、往来报表、资金报表)
 */
class BasicReport implements IReport {

    @Override
    public void operationImpl() {
        System.out.println("基本报表被访问");
    }
}

class IntercourseReport implements IReport {

    @Override
    public void operationImpl() {
        System.out.println("往来报表被访问");
    }
}

class CapitalReport implements IReport {

    @Override
    public void operationImpl() {
        System.out.println("资金报表被访问");
    }
}

/**
 * 3 定义抽象化角色,图形
 */
abstract class AbstractionGraph{
    protected IReport iReport;

    public AbstractionGraph(IReport iReport) {
        this.iReport = iReport;
    }
    abstract void operation();
}

/**
 * 4 定义扩展抽象化角色(柱状图、饼图)
 */
class Barchart extends AbstractionGraph {
    public Barchart(IReport iReport) {
        super(iReport);
    }

    @Override
    void operation() {
        System.out.println("柱状图被访问.");
        iReport.operationImpl();
    }
}

class Piechart extends AbstractionGraph {

    public Piechart(IReport iReport) {
        super(iReport);
    }

    @Override
    void operation() {
        System.out.println("饼图被访问.");
        iReport.operationImpl();
    }
}

image-20211107201008970

优缺点

桥接(Bridge)模式的优点:

  • 抽象与实现分离,扩展能力强
  • 符合开闭原则
  • 符合合成复用原则
  • 其实现细节对客户透明

缺点:由于聚合关系建立在抽象层,要求开发者针对抽象化进行设计与编程,能正确地识别出系统中两个独立变化的维度,这增加了系统的理解与设计难度。

应用场景

当一个类内部具备两种或多种变化维度时,使用桥接模式可以解耦这些变化的维度,使高层代码架构稳定。

桥接模式通常适用于以下场景:

  1. 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时;
  2. 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时;
  3. 当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。

桥接模式的一个常见使用场景就是替换继承。我们知道,继承拥有很多优点,比如,抽象、封装、多态等,父类封装共性,子类实现特性。继承可以很好的实现代码复用(封装)的功能,但这也是继承的一大缺点。

因为父类拥有的方法,子类也会继承得到,无论子类需不需要,这说明继承具备强侵入性(父类代码侵入子类),同时会导致子类臃肿。因此,在设计模式中,有一个原则为优先使用组合/聚合,而不是继承。

桥接模式的扩展

在软件开发中,有时桥接(Bridge)模式可与适配器模式联合使用。当桥接(Bridge)模式的实现化角色的接口与现有类的接口不一致时,可以在二者中间定义一个适配器将二者连接起来,其结构图如下:

图片

源码中的应用

JDBC驱动程序
......
向大神看齐
原文地址:https://www.cnblogs.com/Liu-xing-wu/p/15521584.html