­

设计模式——结构型模式(适配器,桥接,过滤器,组合,装饰器,外观,享元,代理)

  • 2019 年 10 月 20 日
  • 筆記

结构型模式描述如何将类或者对象结合在一起形成更大的结构

可以分为两类:

  1. 类结构型模式。它关心类的组合,由多个类可以组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系
  2. 对象结构型模式。它关心类与对象的组合,通过关联关系使得在一个类中定义另一个类的实例对象,然后通过该对象调用其方法

根据“组合复用原则”,在系统中尽量使用关联关系来替代继承关系,因此大部分结构型模式都是对象结构型模式

一、适配器模式

1、楔子

昨天用刚换的新手机听歌时,发现底部只有 type-c 接口,而博主的耳机是3.5毫米插孔的。好在厂商配备了一个 type-c 转3.5毫米耳机孔,这个转换器就是接下来要讲的适配器模式

2、解析

适配器模式将一个类的接口转换成客户希望的另外一个接口,使接口不兼容的类可以一起工作

模式组成:

  1. 目标角色:定义客户使用的接口
  2. 被适配角色:该角色有一个已存在并使用了的接口,这个接口是需要我们适配的
  3. 适配器角色:适配器模式的核心。将被适配角色已有的接口转换为目标角色希望的接口

在适配器模式中可以定义一个包装类,包装不兼容接口的对象,这个包装类指的就是适配器,它所包装的对象就是适配者,即被适配的类

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

适配器模式可分为两种:类适配器模式和对象适配器模式,区别仅在于适配器角色对被适配角色的适配是通过继承完成的还是通过组合完成的。由于 Java不支持多重继承,且继承会破坏封装,大都提倡使用组合代替继承

3、举例

一个画图程序有绘制点、直线、方块等图形的功能,为了让客户程序使用时不用关心它们的不同,使用一个抽象类规范这些图形的接口

如果现在要绘制圆,发现在系统的其他地方已经有了绘制圆的实现,但系统中的方法和抽象类中规定的方法名称不一样。不管是修改系统中绘制圆的方法,还是抽象类的方法名都极其繁琐,因为出现的地方和次数太多了,这时适配器模式就能大显身手了

添加绘制圆的需求之前,类图如下

KelX3n.png

添加圆的绘制后,类图如下

Ke1SBT.png

由图可知,Shape、Circle、TextCircle之间的关系和适配器模式中 Target、Apater、Apatee之间的关系相对应

下面是 Circle的实现代码

class Circle extends Shape {      //这里引用了 TextCircle      private TextCircle tc;        public Circle() {          tc = new TextCircle(); //初始化      }      void public display() {          tc.display(); //在规定的方法中调用 TextCircle原来的方法      }  }

在适配器角色中不仅可以完成接口转换的过程,还能对功能进行改进和扩展

该模式与代理模式的区别是,后者应用的情况不改变接口命名,而是对已有接口功能的一种控制;前者则强调接口转换

二、桥接模式

1、楔子

系统设计中充满了各种变数,面对不同的变动,我们只能不断的修改设计和代码,然后开始新一轮的测试

那么,我们能采取什么方法来解决变化带给系统的影响呢?可以分析变化的种类,将不变的框架使用抽象类定义出来,然后再将变化的内容使用具体的子类分别实现。这样客户看到的只是一个抽象类,较好的避免了为抽象类现有接口添加新的实现带来的影响,从而缩小了变化带来的影响。但是该方法有一个缺陷:子类数量的爆炸,且某些时候不是很灵活

当一颗几成熟上的一些子树存在了类似的行为,意味着子树中存在几乎重复的功能代码,不妨将行为提取出来,并采用接口的方式提供出来,然后以组合的方式将服务提供给原来的子类。以此达到了前端和被使用的后端独立变化,还实现了后端的重用

这就是桥接模式的诞生

2、解析

桥接模式将抽象部分与它的实现部分分离,使它们都可以独立变化。这里的抽象部分和实现部分指组合关系,即实现部分被抽象部分调用以完成抽象部分的功能

模式组成:

  1. 抽象角色:定义抽象类的接口,维护一个指向实现角色的引用
  2. 精确抽象角色:实现并扩充由抽象角色定义的接口
  3. 实现角色:给出实现类的接口,这里的接口与抽象角色中的接口可以不一致
  4. 具体实现角色:给出实现角色定义接口的具体实现

理解桥接模式,重点需要理解如何将抽象化与实现化脱耦,使得二者可以独立地变化

  • 抽象化:忽略一些信息,把不同的实体当作同样的实体对待。在面向对象中,将对象的共同性质抽取出来形成类的过程即为抽象化的过程
  • 实现化:针对抽象化给出的具体实现。抽象化与实现化是一对互逆的概念,实现化产生的对象比抽象化更具体,是对抽象化事物的进一步具体化
  • 脱耦:将抽象化和实现化之间的耦合解开,或者说将它们之间的强关联改换成弱关联,将两个角色之间的继承关系改为关联关系。桥接模式中的脱耦是指在一个软件系统的抽象化和实现化之间使用关联关系(组合或者聚合关系)而不是继承关系,从而使两者可以相对独立地变化

桥接模式适用场景:

  • 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系
  • 抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合
  • 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展
  • 虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者
  • 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用

3、举例

使用过 Java AWT的人都知道,在不同系统下开发的软件界面带有不同系统的风格,而我们在使用 AWT的 API时根本没有对不同系统进行区分。AWT正是使用桥接模式做到的这一点

由于博主对 AWT了解不深,无法具体讲解,这里用 《Thinking in Patterns with Java》中的教学代码分析

//抽象部分(前端)的抽象角色  class Abstraction {      //维护一个指向实现(Implementor)角色的引用      private Implementation implementation;        public Abstraction(Implementation imp) {          implementation = imp;      }      //接下来定义前端应有的接口      public void service1() {          //使用后端(实现部分)已有的接口          //组合实现功能          implementation.facility1();          implementation.facility2();      }      public void service2() {          implementation.facility2();          implementation.facility3();      }      public void service3() {          implementation.facility1();          implementation.facility2();          implementation.facility4();      }        protected Implementation getImplementation() {          return implementation;      }  }    //抽象部分(前端)的精确抽象角色  class ClientService1 extends Abstraction {      public ClientService1(Implementation imp) {          super(imp);      }      //使用抽象角色提供的方法组合起来完成某项功能,这就是为什么叫精确抽象角色(修正抽象角色)      public void serviceA() {          service1();          service2();      }      public void serviceB() {          service3();      }  }  //另一个精确抽象角色,和上面一样  class ClientService2 extends Abstraction {      ...        //直接通过实现部分的方法来实现一定的功能      public void serviceE() {          getImplementation().facility3();      }  }    //实现部分(后端)的实现角色  interface Implementation {      //该接口只是定义了一定的接口      void facility1();      void facility2();      void facility3();      void facility4();  }    //具体实现角色就是要将实现角色提供的接口实现并完成一定功能,这里略  class Implementation1 implements Implementation {      ...  }

该程序体现出一点特色:不仅实现部分和抽象部分提供的接口可以完全不一样,它们内部的接口也可以完全不一样。但是实现部分要提供类似的功能

三、过滤器模式

1、解析

过滤器模式允许开发人员使用不同的标准来过滤一组对象,通过逻辑运算以解耦的方式把它们连接起来

在管道和过滤器软件体系结构中,每个模块都有一组输入和一组输出。每个模块从它的输入端接收输入数据流,在其内部经过处理后,按照标准的顺序将结果数据流送到输出端以达到传递一组完整的计算结果实例的目的

通常情况下可以通过对输入数据流进行局部变换,并采用渐增式计算方法,在未处理完所有输入数据以前就可以产生部分计算结果,并将其送到输出端口(类似于流水线结构),因此称这种模块为“过滤器“。在这种结构中,各模块之间的连接器充当了数据流的导管,将一个过滤器的输出传到下一个过滤器的输入端,所以这种连接器称为“管道”

模式组成:

  1. 过滤器
    • 输入过滤器:处在问题所在的外部世界与软件系统的边界处,是系统数据流的源点。负责接收外界信息并转化为系统所需的数据流
    • 处理过滤器:系统内变换数据流的部件。它有一个入口和一个出口,数据经入口流入,经过处理过滤器内部处理之后从出口流出
    • 输出过滤器:数据流的终点
  2. 管道

    • 主要功能是连接各个过滤器,充当过滤器之间数据流的通道。具有数据缓冲以及提高过滤器之间的并行性操作的作用

优点:

  • 体现了各功能模块的“黑盘”特性及高内聚、低耦合的特点
  • 可以将整个系统的输入,输出行为看成是多个过滤器行为的简单合成
  • 支持软件功能模块的重用
  • 便于系统维护:新的过滤器可以添加到现有系统中来,旧的可以由改进的过滤器替换
  • 支持某些特定的分析,如吞吐量计算、死锁检测等
  • 支持并行操作,每个过滤器可以作为一个单独的任务完成

缺点:

  • 通常导致系统处理过程的成批操作
  • 需要设计者协调两个相对独立但又存在关系的数据流
  • 可能需要每个过滤器自己完成数据解析和合成工作(如加密和解密),从而导致系统性能下降,并增加了过滤器具体实现的复杂性

2、举例

//创建一个类,在该类上应用标准  public class Person {     private String name;     private String gender;     private String maritalStatus;       public Person(String name,String gender,String maritalStatus){        this.name = name;        this.gender = gender;        this.maritalStatus = maritalStatus;     }     public String getName() {        return name;     }     public String getGender() {        return gender;     }     public String getMaritalStatus() {        return maritalStatus;     }  }    //为标准(Criteria)创建一个接口  import java.util.List;    public interface Criteria {     public List<Person> meetCriteria(List<Person> persons);  }    //创建实现了 Criteria接口的实体类  import java.util.ArrayList;  import java.util.List;    class CriteriaMale implements Criteria {     public List<Person> meetCriteria(List<Person> persons) {        List<Person> malePersons = new ArrayList<Person>();        for (Person person : persons) {           if(person.getGender().equalsIgnoreCase("MALE")){              malePersons.add(person);           }        }        return malePersons;     }  }    class CriteriaFemale implements Criteria {     public List<Person> meetCriteria(List<Person> persons) {        List<Person> femalePersons = new ArrayList<Person>();        for (Person person : persons) {           if(person.getGender().equalsIgnoreCase("FEMALE")){              femalePersons.add(person);           }        }        return femalePersons;     }  }    class CriteriaSingle implements Criteria {     public List<Person> meetCriteria(List<Person> persons) {        List<Person> singlePersons = new ArrayList<Person>();        for (Person person : persons) {           if(person.getMaritalStatus().equalsIgnoreCase("SINGLE")){              singlePersons.add(person);           }        }        return singlePersons;     }  }    class AndCriteria implements Criteria {     private Criteria criteria;     private Criteria otherCriteria;       public AndCriteria(Criteria criteria, Criteria otherCriteria) {        this.criteria = criteria;        this.otherCriteria = otherCriteria;     }     public List<Person> meetCriteria(List<Person> persons) {        List<Person> firstCriteriaPersons = criteria.meetCriteria(persons);        return otherCriteria.meetCriteria(firstCriteriaPersons);     }  }    class OrCriteria implements Criteria {     private Criteria criteria;     private Criteria otherCriteria;       public OrCriteria(Criteria criteria, Criteria otherCriteria) {        this.criteria = criteria;        this.otherCriteria = otherCriteria;     }     public List<Person> meetCriteria(List<Person> persons) {        List<Person> firstCriteriaItems = criteria.meetCriteria(persons);        List<Person> otherCriteriaItems = otherCriteria.meetCriteria(persons);          for (Person person : otherCriteriaItems) {           if(!firstCriteriaItems.contains(person)){             firstCriteriaItems.add(person);           }        }        return firstCriteriaItems;     }  }    //使用不同的标准(Criteria)和它们的结合来过滤 Person对象的列表  import java.util.ArrayList;  import java.util.List;    public class CriteriaPatternDemo {     public static void main(String[] args) {        List<Person> persons = new ArrayList<Person>();          persons.add(new Person("Robert","Male", "Single"));        persons.add(new Person("John","Male", "Married"));        persons.add(new Person("Laura","Female", "Married"));        persons.add(new Person("Diana","Female", "Single"));        persons.add(new Person("Mike","Male", "Single"));        persons.add(new Person("Bobby","Male", "Single"));          Criteria male = new CriteriaMale();        Criteria female = new CriteriaFemale();        Criteria single = new CriteriaSingle();        Criteria singleMale = new AndCriteria(single, male);        Criteria singleOrFemale = new OrCriteria(single, female);          System.out.println("Males: ");        printPersons(male.meetCriteria(persons));          System.out.println("nFemales: ");        printPersons(female.meetCriteria(persons));          System.out.println("nSingle Males: ");        printPersons(singleMale.meetCriteria(persons));          System.out.println("nSingle Or Females: ");        printPersons(singleOrFemale.meetCriteria(persons));     }       public static void printPersons(List<Person> persons){        for (Person person : persons) {           System.out.println("Person : [ Name : " + person.getName()              +", Gender : " + person.getGender()              +", Marital Status : " + person.getMaritalStatus()              +" ]");        }     }  }

四、组合模式

1、楔子

在数据结构课程中,树是非常重要的章节。其定义如下

树是 n(n>=0)个结点的有限集 T,T为空时称为空树,否则它满足如下两个条件:

  1. 有且仅有一个特定的称为根的结点
  2. 其余的结点可分为 m(m>=0)个互不相交的子集 T1, T2, …, Tm,其中每个子集本身又是一棵树,并称其为根的子树

该递归定义刻画了树的固有属性:一颗非空树由若干棵子树构成,子树又能分成若干更小的子树,这些子树既可以是叶子也可以是分支

写下来学习的组合模式就和树型结构、递归关系有一定关系

2、解析

组合模式将对象以树形结构组织起来,以达成“部分—整体”的层次结构,使得客户端对单个对象和组合对象的使用具有一致性

模式组成:

  1. 抽象构件角色:为组合中的对象声明接口,也可以为共有接口实现缺省行为
  2. 树叶构件角色:在组合中表示叶节点对象——没有子节点,实现抽象构件角色声明的接口
  3. 树枝构件角色:在组合中表示分支节点对象——有子节点,实现抽象构件角色声明的接口;存储子部件

组合模式的关键是定义了一个抽象构件类,它既可以代表叶子,又可以代表容器,而客户端针对该抽象构件类进行编程,无须知道它到底表示的是叶子还是容器,可以对其进行统一处理

同时容器对象与抽象构件类之间还建立一个聚合关联关系,在容器对象中既可以包含叶子,也可以包含容器,以此实现递归组合,形成一个树形结构

优点:

  • 使客户端调用简单,客户端可以一致的使用组合结构或其中单个对象,用户不必关心自己处理的是单个对象还是整个组合结构,这就简化了客户端代码
  • 更容易在组合体内加入对象部件。客户端不必因为加入了新的对象部件而更改代码

缺点:不容易限制组合中的构件

3、举例

JUnit是一个单元测试框架,按照此框架的规范编写测试代码可以使单元测试自动化。为达到自动化的目的,JUnit定义了两个概念:TestCase和 TestSuite。前者是编写的测试类,后者是一个不同 TestCase的集合,当然这个集合里可以包含 TestSuite元素,这样运行一个 TestSuite会将其包含的 TestCase全部运行

但是在真实运行测试程序时不需要关心这个类是 TestCase还是 TestSuite,只关心测试运行结果如何。这就是 JUnit使用组合模式的原因

JUnit为了采用组合模式将 TestCase和 TestSuite统一起来,创建了一个 Test接口来扮演抽象构件角色,这样原来的 TestCase扮演组合模式中的树叶构件角色,而 TestSuite扮演组合模式中的树枝构件角色

//Test接口——抽象构件角色  interface Test {      /**       * Counts the number of test cases that will be run by this test.       */      public abstract int countTestCase();      /**       * Runs a test and collects its result in a TestResult instance.       */      public abstract void run(TestResult result);  }    //TestSuite类的部分有关源码——Composite角色,实现了接口 Test  class TestSuite implements Test {      //使用较老的 Vector保存添加的 test      private Vector fTests = new Vector(10);      private String fName;        /**       * Adds a test to the suite.       */      public void addTest(Test test) {          //这里的参数是 Test类型的,意味着 TestCase和 TestSuite以及以后实现 Test接口的任何类都可以被添加进来          fTests.addElement(test);      }      /**       * Counts the number of test cases that will be run by this test.       */      public int countTestCase() {          int count= 0;          for (Enumeration e= tests(); e.hasMoreElements(); ) {              Test test= (Test)e.nextElement();              count= count + test.countTestCases();          }          return count;      }      /**       * Runs the tests and collects their result in a TestResult.       */      public void run(TestResult result) {          for (Enumeration e= tests(); e.hasMoreElements(); ) {              if (result.shouldStop() )                  break;              Test test= (Test)e.nextElement();              //关键在这个方法              runTest(test, result);          }      }      //该方法中是递归的调用,至于 Test是什么类型只有运行时得知      public void runTest(Test test, TestResult result) {          test.run(result);      }  }    //TestCase类的部分有关源码——Leaf角色,编写的测试类就是继承于它  abstract class TestCase extends Assert implements Test {      /**       * Counts the number of test cases executed by run(TestResult result).       */      public int countTestCases() {          return 1;      }      /**       * Runs the test case and collects the results in TestResult.       */      public void run(TestResult result) {          result.run(this);      }  }

五、装饰器模式

1、楔子

“装饰”一词,肯定让你想到了又黑又火的家庭装修。两者在道理上有很多相似之处。家庭装修就是在实而不华的墙面上涂抹一层华而不实的颜料,看起来多姿多彩。但是墙仍旧是墙,本质并没有发生变化

2、解析

装饰器模式动态地给一个对象添加一些额外的功能。添加的方式是对客户透明的,因此客户端并不会觉得对象在装饰前和装饰后有什么不同。这样装饰器模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展

模式组成:

  1. 抽象构件角色:定义一个抽象接口,规范准备接受附加功能的对象
  2. 具体构件角色:被装饰者,定义一个将要被装饰增加功能的类
  3. 装饰角色:持有一个构件角色的实例,并定义了抽象构件定义的接口
  4. 具体装饰角色:给构件增加功能

一般有两种方式可以实现给一个类或对象增加行为:

  • 继承机制(通过继承一个现有类可以使得子类在拥有自身方法的同时还拥有父类的方法)
  • 关联机制(将一个类的对象嵌入另一个对象中,由另一个对象决定是否调用嵌入对象的行为以便扩展自己的行为)

与继承关系相比,关联关系的优势在于不会破坏类的封装性,而且继承是一种耦合度较大的静态关系,无法在程序运行时动态扩展。使用装饰器模式来实现扩展比继承更加灵活,可以在不需要创造更多子类的情况下,将对象的功能加以扩展

组合模式侧重通过递归组合构造类,使不同的对象、多重的对象可以“一视同仁”;装饰器模式仅仅是借递归组合来达到定义中的目的

3、举例

JUnit中 TestCase是一个很重要的类,允许对其进行进行功能扩展

在 junit.extensions包中,TestDecorator、RepeatedTest就是对 TestCase的装饰器模式扩展

//抽象构件角色  interface Test {      /**       * Counts the number of test cases that will be run by this test.       */      public abstract int countTestCases();      /**       * Runs a test and collects its result in a TestResult instance.       */      public abstract void run(TestResult result);  }    //具体构件对象,这里是抽象类  abstract class TestCase extends Assert implements Test {      public int countTestCases() {          return 1;      }      public TestResult run() {          TestResult result = createResult();          run(result);          return result;      }      public void run(TestResult result) {          result.run(this);      }  }    //装饰角色  class TestDecorator extends Assert implements Test {      //按照上面的要求保留了一个对构件对象的实例      protected Test fTest;        public TestDecorator(Test test) {          fTest = test;      }      /**       * The basic run behaviour.       */      public void basicRun(TestResult result) {          fTest.run(result);      }      public int countTestCases() {          return fTest.countTestCases();      }      public void run(TestResult result) {          basicRun(result);      }      public String toString() {          return fTest.toString();      }      public Test getTest() {          return fTest;      }  }    //具体装饰角色。该类的作用是设置测试类的执行次数  class RepeatedTest extends TestDecorator {      private int fTimesRepeat;        public RepeatedTest(Test test, int repeat) {          super(test);          if (repeat < 0)              throw new IllegalArgumentException("Repetition count must be > 0");          fTimesRepeat = repeat;      }      //如何进行装饰      public int countTestCases() {          return super.countTestCases()*fTimesRepeat;      }      public void run(TestResult result) {          for (int i= 0; i < fTimesRepeat; i+ + ) {              if (result.shouldStop())                  break;              super.run(result);          }      }      public String toString() {          return super.toString() + "(repeated)";      }  }  /*  使用时,采用如下方式:  TestDecorator test = new RepeatedTest(new TestXXX(), 3);  */

六、外观模式

1、解析

外观模式:外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面

模式组成:

  1. 外观角色:外观模式的核心。被用户角色调用,因此熟悉子系统的功能。其内部根据客户角色已有的需求预定了几种功能组合
  2. 子系统角色:实现子系统的功能。对它而言,外观角色和客户角色是未知的,它没有任何外观角色的信息和链接
  3. 客户角色:调用外观角色完成要得到的功能

根据“单一职责原则”,在软件中将一个系统划分为若干个子系统有利于降低整个系统的复杂性。常见的设计目标是使子系统间的通信和相互依赖关系达到最小,而达到该目标的途径之一就是引入一个外观对象,它为子系统的访问提供了一个简单而单一的入口

外观模式也是“迪米特法则”的体现,通过引入一个新的外观类可以降低原有系统的复杂度,同时降低客户类与子系统类的耦合度

外观模式要求一个子系统的外部与其内部的通信通过一个统一的外观对象进行,外观类将客户端与子系统的内部复杂性分隔开,使得客户端只需要与外观对象打交道,而不需要与子系统内部的很多对象打交道

外观模式适用场景:

  • 为一个复杂子系统提供一个简单接口
  • 客户程序与抽象类的实现部分之间存在很大的依赖性
  • 需要构建一个层次结构的子系统时,使用外观模式定义子系统中每层的入口点

优点:

  • 对客户屏蔽子系统组件,减少了客户处理的对象的数目,使子系统使用更加方便
  • 实现了子系统与客户之间的松耦合关系,而子系统内部的功能组件往往是紧耦合的

2、举例

典型应用是进行数据库连接

一般每次对数据库进行访问时都要执行如下操作:先得到 connect实例,然后打开 connect获得链接,得到一个 statement,执行 sql语句进行查询,得到查询结果集

将步骤提取出来并封装到一个类中,这样每次执行数据库访问时只需要将必要的参数传递到类中即可

七、享元模式

1、楔子

面向对象技术可以很好地解决一些灵活性或可扩展性问题,但在很多情况下需要在系统中增加类和对象的个数。当对象数量太多时,将导致运行代价过高,带来性能下降等问题

享元模式正是为解决这一类问题而诞生的,它通过共享技术实现相同或相似对象的重用

2、解析

享元模式:运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用

在享元模式中可以共享的相同内容称为内部状态,需要外部环境来设置的不能共享的内容称为外部状态。可以通过设置不同的外部状态使得相同的对象具有不同的特征,而相同的内部状态是可以共享的

在享元模式中通常会出现工厂模式,需要创建一个享元工厂来负责维护一个享元池,用于存储具有相同内部状态的享元对象

在实际使用中,能够共享的内部状态是有限的,因此享元对象一般都设计为较小的对象,它所包含的内部状态较少,这种对象也称为细粒度对象

享元模式主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式

优点:大大减少对象的创建,降低系统的内存,使效率提高

缺点:

  • 提高了系统的复杂度
  • 需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱

享元模式试用场景:

  • 系统有大量相似对象
  • 需要缓冲池的场景

3、举例

import java.util.HashMap;  import java.util.Map;    public class Test {      public static void main(String args[]){          String yundong ="足球";          for(int i=0;i<5;i++){              TiYuGuan tiYuGuan = JianZhuFactory.getTyg(yundong);              tiYuGuan.setName("合肥体育馆");              tiYuGuan.setShape("椭圆形");              tiYuGuan.setYongtu("比赛");              tiYuGuan.use();          }      }  }    //定义一个建筑接口  interface Jianzhu{      void use();  }    //创建一个体育馆  class TiYuGuan implements Jianzhu{      private String name;      private String shape;      private String yongtu;      private String yundong;        public TiYuGuan(String yundong) {          this.yundong = yundong;      }      public String getYongtu() {          return yongtu;      }      public void setYongtu(String yongtu) {          this.yongtu = yongtu;      }      public String getName() {          return name;      }      public void setName(String name) {          this.name = name;      }      public String getShape() {          return shape;      }      public void setShape(String shape) {          this.shape = shape;      }      public String getYundong() {          return yundong;      }      public void setYundong(String yundong) {          this.yundong = yundong;      }        public void use() {          System.out.println("该体育馆被使用于"+yongtu+",项目为:"+ yundong+",场地形状为:"+shape+",场地名称为:"+name+",对象:"+this);      }  }    //需要用到工厂类 建筑工厂 可以产出体育馆等建筑  class JianZhuFactory{      private static final Map<String,TiYuGuan> tygs =new HashMap<String,TiYuGuan>();      public static TiYuGuan getTyg(String yundong){          TiYuGuan tyg = tygs.get(yundong);          if(tyg==null){              tygs.put(yundong,new TiYuGuan(yundong));          }          return tygs.get(yundong);      }      public static int getSize(){          return tygs.size();      }  }

八、代理模式

1、楔子

在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不能看到的内容和服务或者添加客户需要的额外服务

2、解析

代理模式为其他对象提供一种代理以控制对这个对象的访问

模式组成:

  1. 抽象主题角色:声明真实主题和代理主题的共同接口
  2. 代理主题角色:内部包含对真实主题的引用,并且提供和真实主题角色相同的接口
  3. 真实主题角色:定义真实的对象

代理模式分为8种,这里介绍常见的几个:

  1. 远程代理:为一个位于不同地址空间的对象提供一个局域代表对象
  2. 虚拟代理:使用一个资源消耗很大或者比较复杂的对象产生很大延迟时才创建
  3. 保护代理:控制对一个对象的访问权限
  4. 智能引用代理:提供比对目标对象额外的服务。乳记录访问的流量,提供友情提示等

3、举例

以论坛为例。注册用户拥有发帖,修改个人信息,修改帖子等功能;游客只能看别人发的帖子,没有其他权限。为了简化代码,这里只实现发帖权限的控制

首先实现一个抽象主题角色 MyForum,其中定义了真实主题和代理主题的共同接口——发帖功能

public interface MyForum {      public void AddFile();  }

真实主题角色和代理主题角色都要实现该接口,前者基本是将这个接口的方法内容填充起来,这里不再赘述,主要实现代理主题角色

public class MyForumProxy implements MyForum {      private RealMyForum forum = new RealMyForum() ;      private int permission ; //权限值        public MyForumProxy(int permission) {          this.permission = permission ;      }      //实现的接口      public void AddFile() {          //满足权限设置时才执行操作          //Constants是一个常量类          if(Constants.ASSOCIATOR = = permission) {              forum.AddFile();          }          else              System.out.println("You are not a associator of MyForum, please registe!");      }  }