欢迎访问shiker.tech

请允许在我们的网站上展示广告

您似乎使用了广告拦截器,请关闭广告拦截器。我们的网站依靠广告获取资金。

结构型设计模式实战【二】-外观、享元、代理、装饰器
(last modified Sep 8, 2022, 1:04 AM )
by
侧边栏壁纸
  • 累计撰写 178 篇文章
  • 累计创建 62 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

结构型设计模式实战【二】-外观、享元、代理、装饰器

橙序员
2022-08-21 / 0 评论 / 0 点赞 / 423 阅读 / 5,143 字 / 正在检测百度是否收录... 正在检测必应是否收录...
文章摘要(AI生成)

外观/门面模式定义为子系统中的一组接口提供一个统一的接口角色窗口类(facade):代表构成系统的许多其他角色的简单窗口。向系统外部提供统一API构成系统的许多其他角色:这些角色各自完成自己的工作,并不知道facade角色请求者(client):负责调用facade角色spring实现在以下示例中,

外观/门面模式

定义

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

优缺点

优点

  • 减少系统的相互依赖

    想想看,如果我们不使用门面模式,外界访问直接深入到子系统内部,相互之间是一种强耦合关系,你死我就死,你活我才能活,这样的强依赖是系统设计所不能接受的,门面模式的出现就很好地解决了该问题,所有的依赖都是对门面对象的依赖,与子系统无关。

  • 提高了灵活性

    依赖减少了,灵活性自然提高了。不管子系统内部如何变化,只要不影响到门面对象,任你自由活动。

  • 提高安全性

    想让你访问子系统的哪些业务就开通哪些逻辑,不在门面上开通的方法,你休想访问到。

缺点

门面模式最大的缺点就是不符合开闭原则,对修改关闭,对扩展开放,看看我们那个门 面对象吧,它可是重中之重,一旦在系统投产后发现有一个小错误,你怎么解决?完全遵从 开闭原则,根本没办法解决。继承?覆写?都顶不上用,唯一能做的一件事就是修改门面角 色的代码,这个风险相当大,这就需要大家在设计的时候慎之又慎,多思考几遍才会有好收获

使用场景

  • 为一个复杂的模块或子系统提供一个供外界访问的接口

  • 子系统相对独立——外界对子系统的访问只要黑箱操作即可

    比如利息的计算问题,没有深厚的业务知识和扎实的技术水平是不可能开发出该子系统的,但是对于使用该系统的开发人员来说,他需要做的就是输入金额以及存期,其他的都不用关心,返回的结果就是利息,这时候,门面模式是非使用不可了。

  • 预防低水平人员带来的风险扩散

    比如一个低水平的技术人员参与项目开发,为降低个人代码质量对整体项目的影响风险,一般的做法是"画地为牢”,只能在指定的子系统中开发,然后再提供门面接口进行访问操作。

最佳实践

门面模式是一个很好的封装方法,一个子系统比较复杂时,比如算法或者业务比较复杂,就可以封装出一个或多个门面出来,项目的结构简单,而且扩展性非常好。还有,对于 一个较大项目,为了避免人员带来的风险,也可以使用门面模式,技术水平比较差的成员, 尽量安排独立的模块,然后把他写的程序封装到一个门面里,尽量让其他项目成员不用看到 这些人的代码,看也看不懂,我也遇到过一个“高人”写的代码,private方法、构造函数、常 量基本都不用,你要一个public方法,好,一个类里就一个public方法,所有代码都在里面, 然后你就看吧,一大坨程序,看着就能把人逼疯。使用门面模式后,对门面进行单元测试, 约束项目成员的代码质量,对项目整体质量的提升也是一个比较好的帮助。

角色

  • 窗口类(facade):代表构成系统的许多其他角色的简单窗口。向系统外部提供统一API

  • 构成系统的许多其他角色:这些角色各自完成自己的工作,并不知道facade角色

  • 请求者(client):负责调用facade角色

image-20220821162314507

spring实现

在以下示例中,我们将要编写一个web页面,编写过程需要有从邮件中获取用户名字的数据库类,编写html文件的类,以及一个提供高层接口的类

多角色

从邮件中获取用户名字的数据库类:

public class Database {

   public static Properties getProperties(String dbname){
      String filename = dbname + ".txt";
      Properties prop =  new Properties();
      try{
         File file = ResourceUtils.getFile("classpath:templates/"+ filename);
         prop.load(Files.newInputStream(Paths.get(file.getPath())));
      }catch(IOException e){
         System.out.println("Warning: "+filename+" is not found");
      }
      return prop;
   }
}

编写html文件的类

@Component
public class HtmlWriter {

   private Writer writer ;

   public void setWriter(Writer writer) {
      this.writer = writer;
   }

   public void title(String title) throws IOException{
      writer.write("<html>");
      writer.write("<head>");
      writer.write("<title>"+title+"</title>");
      writer.write("</head>");
      writer.write("<body>\n");
      writer.write("<h1>"+title+"</h1>\n");
   }
   public void paragraph(String msg) throws IOException{
      writer.write("<p>"+msg+"</p>\n");
   }
   public void link(String href,String caption) throws IOException{
      paragraph("<a href=\""+href+"\">"+caption+"</a>");
   }
   public void mailto(String mailaddr,String username) throws IOException{
      link("mailto:"+mailaddr, username);
   }
   public void close() throws IOException{
      writer.write("</body>");
      writer.write("</html>\n");
      writer.close();
   }
}

窗口类

提供统一的创建邮件欢迎页面的接口

@Component
@Slf4j
public class PageMaker {

   @Resource
   private HtmlWriter writer;

   public void makeWelcomePage(String mailaddr,String filename, String target){
      try{
         Properties mailprop = Database.getProperties(filename);
         String username = mailprop.getProperty(mailaddr);
         File file = ResourceUtils.getFile("classpath:templates/"+ target + ".html");
         writer.setWriter(new FileWriter(file));
         writer.title("Welcome to "+username+"'s page");
         writer.paragraph(username+"欢迎来到"+username+"的主页");
         writer.paragraph("等着你的邮件");
         writer.mailto(mailaddr, username);
         writer.close();
         log.info(target+" is created for "+mailaddr+" ("+username+")");
      }catch(Exception e){
         e.printStackTrace();
      }
   }
}

测试

@Controller
public class FacadeController {

   @Resource
   private PageMaker pageMaker;

   @GetMapping("/facade")
   public String facade(){
      pageMaker.makeWelcomePage("hyuki@hyuki.com", "maildata", "hello");
      return "hello";
   }
}

测试结果为

http://localhost:8080/facade

HTTP/1.1 200 
Content-Type: text/html;charset=UTF-8
Content-Language: zh-CN
Transfer-Encoding: chunked
Date: Sun, 21 Aug 2022 08:50:24 GMT
Keep-Alive: timeout=60
Connection: keep-alive

<html>
<head><title>Welcome to Hiroshi Yuki's page</title></head>
<body>
<h1>Welcome to Hiroshi Yuki's page</h1>
<p>Hiroshi Yuki欢迎来到Hiroshi Yuki的主页</p>
<p>等着你的邮件</p>
<p><a href="mailto:hyuki@hyuki.com">Hiroshi Yuki</a></p>
</body>
</html>

享元模式

定义

运用共享对象有效地支持大量细粒度的对象

优点

享元模式是一个非常简单的模式,它可以大大减少应用程序创建的对象,降低程序内存 的占用,增强程序的性能,但它同时也提高了系统复杂性,需要分离出外部状态和内部状态,而且外部状态具有固化特性,不应该随内部状态改变而改变,否则导致系统的逻辑混乱。

使用场景

在如下场景中则可以选择使用享元模式。

  • 系统中存在大量的相似对象。
  • 细粒度的对象都具备较接近的外部状态,而且内部状态与环境无关,也就是说对象没有特定身份。
  • 需要缓冲池的场景。

最佳实践

享元模式在Java API中也是随处可见,String类的intern方法就是一个很好的例子

虽然可以使用享元模式可以实现对象池,但是这两者还是有比较大 的差异,对象池着重在对象的复用上,池中的每个对象是可替换的,从同一个池中获得A对 象和B对象对客户端来说是完全相同的,它主要解决复用,而享元模式在主要解决的对象的 共享问题,如何建立多个可共享的细粒度对象则是其关注的重点

角色

  • 享元角色(flyweight):指的是会被共享的实例

  • 享元工厂(flyweightFactory):生成享元实例的工厂

  • 请求者(client):请求调用工厂生成享元角色

image-20220821165812934

spring实现

将许多普通字符组合成为大型字符,根据我们的输入以供程序打印使用

享元角色

表示大型字符的类

@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
@Slf4j
public class BigChar {

   private char charname;
   private String fontdata;

   public void initBigChar(char charname){
      this.charname = charname;
      try{
         File file = ResourceUtils.getFile("classpath:static/big"+charname+".txt");
         BufferedReader reader = new BufferedReader(new FileReader(file));
         String line;
         StringBuffer buf = new StringBuffer();
         while((line=reader.readLine())!=null){
            buf.append(line);
            buf.append("\n");
         }
         reader.close();
         this.fontdata = buf.toString();
      }catch(IOException e){
         this.fontdata = charname+"?";
      }
      
   }
   
   public void print(){
      log.info(fontdata);
   }
}

享元工厂

存储并生成大型字符的工厂

@Component
public abstract class BigCharFactory {

   private HashMap<String, BigChar> pool = new HashMap<String, BigChar>();

   @Lookup
   public abstract BigChar bigChar();

   public BigChar genBigChar(char charName){
      BigChar bigChar = bigChar();
      bigChar.initBigChar(charName);
      return bigChar;
   }

    /**
     * 类似与spring中beanfactory的创建
     * @param charname
     * @return
     */
    public synchronized BigChar getBigChar(char charname){
       BigChar bc = pool.get(""+charname);
       if(bc==null){
          bc = genBigChar(charname);
          pool.put(""+charname, bc);
       }
       return bc;
    }
}

使用者

表示多个bigchar组成的大型字符串的类

@Component
public class BigString {

   private BigChar[] bigchars;

   @Resource
   private BigCharFactory factory;

   public void initBigString(String string) {
      bigchars = new BigChar[string.length()];
      for(int i =0 ;i<bigchars.length;i++){
         bigchars[i] = factory.getBigChar(string.charAt(i));
      }
   }
   
   public void print(){
      for(int i=0;i<bigchars.length;i++){
         bigchars[i].print();
      }
   }
}

测试

@RestController
public class FlyweightController {

   @Resource
   private BigString bigString;

   @GetMapping("/flyweight")
   public String flyweight(String param){
      bigString.initBigString(param);
      bigString.print();
      return "success";
   }
}

当请求为GET http://localhost:8080/flyweight?param=12113 时,输出结果为:

2022-08-21 17:13:58.179  INFO 23216 --- [nio-8080-exec-1] c.e.designpattern.Flyweight.BigChar      : ......##........
..######........
......##........
......##........
......##........
......##........
..##########....
................

2022-08-21 17:13:58.179  INFO 23216 --- [nio-8080-exec-1] c.e.designpattern.Flyweight.BigChar      : ....######......
..##......##....
..........##....
......####......
....##..........
..##............
..##########....
................

2022-08-21 17:13:58.179  INFO 23216 --- [nio-8080-exec-1] c.e.designpattern.Flyweight.BigChar      : ......##........
..######........
......##........
......##........
......##........
......##........
..##########....
................

2022-08-21 17:13:58.179  INFO 23216 --- [nio-8080-exec-1] c.e.designpattern.Flyweight.BigChar      : ......##........
..######........
......##........
......##........
......##........
......##........
..##########....
................

2022-08-21 17:13:58.179  INFO 23216 --- [nio-8080-exec-1] c.e.designpattern.Flyweight.BigChar      : ....######......
..##......##....
..........##....
......####......
..........##....
..##......##....
....######......
................

代理模式

定义

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

优点

  • 职责清晰

    真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件事务,附带的结果就是编程简洁清晰。

  • 高扩展性

    具体主题角色是随时都会发生变化的,只要它实现了接口,甭管它如何变化,都逃不脱如来佛的手掌(接口),那我们的代理类完全就可以在不做任何修改的情况下使用。

  • 智能化

    例如Struts通过动态代理把表单元素映射到对象

使用场景

代理模 式的使用场景非常多,大家可以看看Spring AOP,这是一个非常典型的动态代理

最佳实践

代理模式应用得非常广泛,大到一个系统框架、企业平台,小到代码片段、事务处理, 稍不留意就用到代理模式。可能该模式是大家接触最多的模式,而且有了AOP大家写代理就 更加简单了,有类似Spring AOP和AspectJ这样非常优秀的工具,拿来主义即可!不过,大家可以看看源代码,特别是调试时,只要看到类似$Proxy0这样的结构,你就应该知道这是一 个动态代理了。

角色

  • 主体(subject):定义了是代理角色和实际角色之间具有一致性的接口
  • 代理人(proxy):尽量处理请求,只在必要时生成实际角色
  • 实际主体(realSubject):在代理人无法胜任时才会用到
  • 请求者:使用代理模式的角色

image-20220821172401410

静态代理实现

下面我们来看一段使用了Proxy模式的示例程序。这段示例程序实现了一个“带名字的打印机”。说是打印机,其实只是将文字显示在界面上而已。在Main类中会生成PrinterProxy类的实例(即“代理人”)。首先我们会给实例赋予名字Alice并在界面中显示该名字。接着会将实例名字改为Bob,然后显示该名字。在设置和获取名字时,都不会生成真正的Printer类的实例(即本人),而是由PrinterProxy类代理。最后,直到我们调用print方法,开始进入实际打印阶段后,PrinterProxy类才会生成Printer类的实例。

主体

public interface Printable {

   void setPrinterName(String name);
   String getPrinterName();
   void print(String string);
}

实际主体

@Slf4j
public class Printer implements Printable {

   private String name;

   public Printer(String name) {
      super();
      this.name = name;
      heavyJob("Printer的实例生成中("+name+")");
   }

   private void heavyJob(String msg) {
      // TODO Auto-generated method stub
      System.out.println(msg);
      for(int i =0;i<5;i++){
         try{
            Thread.sleep(1000);
         }catch(InterruptedException e){
            
         }
         log.info(".");
      }
      log.info("结束。");
   }

   @Override
   public void setPrinterName(String name) {
      this.name = name;
   }

   @Override
   public String getPrinterName() {
      // TODO Auto-generated method stub
      return name;
   }

   @Override
   public void print(String string) {
      log.info("=== "+name+" ===");
      log.info(string);
   }

}

代理人

public class PrinterProxy implements Printable {
   private String name;

   private Printer real;

   public PrinterProxy(String name) {
      super();
      this.name = name;
   }

   @Override
   public void setPrinterName(String name) {
      if(real!=null){
         real.setPrinterName(name);
      }
      this.name = name;
   }

   @Override
   public String getPrinterName() {
      // TODO Auto-generated method stub
      return name;
   }

   @Override
   public void print(String string) {
      realize();
      real.print(string);
   }

   private synchronized void realize() {
      // TODO Auto-generated method stub
      if(real == null){
         real = new Printer(name);
      }
   }

}

调用者

public class Main {
   public static void main(String[] args) {
      Printable p =new PrinterProxy("Alice");
      System.out.println("现在的名字是"+p.getPrinterName()+"。");
      p.setPrinterName("Bob");
      System.out.println("现在的名字是"+p.getPrinterName()+"。");
      p.print("hello, world.");
   }
}

装饰器模式

定义

动态地给一个对象添加一些额外的职责或者行为

优缺点

优点

  • 装饰类和被装饰类可以独立发展,而不会相互耦合。换句话说,Component类无须知 道Decorator类,Decorator类是从外部来扩展Component类的功能,而Decorator也不用知道具 体的构件。
  • 装饰模式是继承关系的一个替代方案。我们看装饰类Decorator,不管装饰多少层,返 回的对象还是Component,实现的还是is-a的关系。
  • 装饰模式可以动态地扩展一个实现类的功能,这不需要多说,装饰模式的定义就是如 此。

缺点

对于装饰模式记住一点就足够了:多层的装饰是比较复杂的。为什么会复杂呢?你想想 看,就像剥洋葱一样,你剥到了最后才发现是最里层的装饰出现了问题,想象一下工作量 吧,因此,尽量减少装饰类的数量,以便降低系统的复杂度。

使用场景

  • 需要扩展一个类的功能,或给一个类增加附加功能。
  • 需要动态地给一个对象增加功能,这些功能可以再动态地撤销。
  • 需要为一批的兄弟类进行改装或加装功能,当然是首选装饰模式

最佳实践

装饰模式是对继承的有力补充。你要知道继承不是万能的,继承可以解决实际的问题, 但是在项目中你要考虑诸如易维护、易扩展、易复用等,而且在一些情况下你要是用继承就会增加很多子类,而且灵活性非常差,那当然维护也不容易了,也就是说装饰模式可以替代继承,解决我们类膨胀的问题。同时,你还要知道继承是静 态地给类增加功能,而装饰模式则是动态地增加功能

装饰模式还有一个非常好的优点:扩展性非常好。在一个项目中,你会有非常多的因素 考虑不到,特别是业务的变更,不时地冒出一个需求,尤其是提出一个令项目大量延迟的需 求时,那种心情是相当的难受!装饰模式可以给我们很好的帮助,通过装饰模式重新封装一 个类,而不是通过继承来完成

角色

  • 组件实例(Component):增加功能时的核心角色。
  • 具体组件(ConcreteComponent):该角色是实现了Component角色所定义的接口(API)的具体实例。
  • 装饰器(Decorator):该角色具有与Component 角色相同的接口(API)。在它内部保存了被装饰对象——Component角色。Decorator角色知道自己要装饰的对象。
  • 具体装饰器(ConcreteDecorator):该角色是具体的Decorator角色。

image-20220821180844728

spring实现

本示例程序的功能是给文字添加装饰边框。

组件实例

用于显示字符串的抽象类

@Slf4j
public abstract class Display {
   public abstract int getColumns();
   public abstract int getRows();
   public abstract String getRowText(int row);
   public final void show(){
      for(int i=0;i<getRows();i++){
         log.info(getRowText(i));
      }
   }
}

具体组件

用于显示单行字符串的类

@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class StringDisplay extends Display {
   private String string;

   public void initStringDisplay(String string) {
      this.string = string;
   }

   @Override
   public int getColumns() {
      // TODO Auto-generated method stub
      return string.getBytes().length;
   }

   @Override
   public int getRows() {
      // TODO Auto-generated method stub
      return 1;
   }

   @Override
   public String getRowText(int row) {
      // TODO Auto-generated method stub
      if (row == 0) {
         return string;
      } else {
         return null;
      }
   }

}

装饰器

用于显示边框的抽象类

public abstract class Border extends Display {
   protected Display display;

   protected void initBorder(Display display) {
      this.display = display;
   }
}

具体装饰器

用于显示上下左右边框的类

@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class FullBorder extends Border{

   protected void initFullBorder(Display display) {
      initBorder(display);
      // TODO Auto-generated constructor stub
   }

   @Override
   public int getColumns() {
      // TODO Auto-generated method stub
      return 1+display.getColumns()+1;
   }

   @Override
   public int getRows() {
      // TODO Auto-generated method stub
      return 1+display.getRows()+1;
   }

   @Override
   public String getRowText(int row) {
      // TODO Auto-generated method stub
      if(row==0){
         return "+"+makeLine('-',display.getColumns())+"+";
      }else if(row == display.getRows()+1){
         return "+"+makeLine('-', display.getColumns())+"+";
      }else{
         return "|"+display.getRowText(row-1)+"|";
      }
   }

   private String makeLine(char ch,int count){
      StringBuffer buf = new StringBuffer();
      for (int i = 0; i < count; i++) {
         buf.append(ch);
      }
      return buf.toString();
   }
   
}

只显示左右边框的类


@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class SideBorder extends Border{

   private char borderChar;
   public void initSideBorder(Display display,char ch) {
      initBorder(display);
      // TODO Auto-generated constructor stub
      this.borderChar = ch;
   }

   @Override
   public int getColumns() {
      // TODO Auto-generated method stub
      return 1+display.getColumns()+1;
   }

   @Override
   public int getRows() {
      // TODO Auto-generated method stub
      return display.getRows();
   }

   @Override
   public String getRowText(int row) {
      // TODO Auto-generated method stub
      return borderChar+display.getRowText(row)+borderChar;
   }

}

实例生成器

由具体组件或装饰器生成组件的生成器

@Component
public abstract class DisplayGenerator {

   @Lookup
   protected abstract StringDisplay stringDisplay();

   @Lookup
   protected abstract SideBorder sideBorder();

   @Lookup
   protected abstract FullBorder fullBorder();

   public Display getStringDisplay(String s){
      StringDisplay stringDisplay = stringDisplay();
      stringDisplay.initStringDisplay(s);
      return stringDisplay;
   }

   public Display getFullBorder(Display display){
      FullBorder fullBorder = fullBorder();
      fullBorder.initFullBorder(display);
      return fullBorder;
   }

   public Display getSideBorder(Display display, char c){
      SideBorder sideBorder = sideBorder();
      sideBorder.initSideBorder(display, c);
      return sideBorder;
   }
}

测试

@RestController
public class DecoratorController {

   @Resource
   DisplayGenerator displayGenerator;

   @GetMapping("decorate")
   public String decorate(){
      Display b1 = displayGenerator.getStringDisplay("hello, world.");
      Display b2 = displayGenerator.getSideBorder(b1, '#');
      Display b3 = displayGenerator.getFullBorder(b2);
      b1.show();
      b2.show();
      b3.show();
      Display b4 = displayGenerator.getSideBorder(displayGenerator.getFullBorder(
            displayGenerator.getFullBorder(displayGenerator.getSideBorder(displayGenerator.getFullBorder(displayGenerator.getStringDisplay(
                  "你好,世界")), '*'))), '/');
      b4.show();
      return "success";
   }
}

测试结果如下:

2022-08-21 18:02:59.615  INFO 4812 --- [nio-8080-exec-1] c.e.designpattern.Decorator.Display      : hello, world.
2022-08-21 18:02:59.615  INFO 4812 --- [nio-8080-exec-1] c.e.designpattern.Decorator.Display      : #hello, world.#
2022-08-21 18:02:59.615  INFO 4812 --- [nio-8080-exec-1] c.e.designpattern.Decorator.Display      : +---------------+
2022-08-21 18:02:59.615  INFO 4812 --- [nio-8080-exec-1] c.e.designpattern.Decorator.Display      : |#hello, world.#|
2022-08-21 18:02:59.615  INFO 4812 --- [nio-8080-exec-1] c.e.designpattern.Decorator.Display      : +---------------+
2022-08-21 18:02:59.615  INFO 4812 --- [nio-8080-exec-1] c.e.designpattern.Decorator.Display      : /+---------------------+/
2022-08-21 18:02:59.616  INFO 4812 --- [nio-8080-exec-1] c.e.designpattern.Decorator.Display      : /|+-------------------+|/
2022-08-21 18:02:59.616  INFO 4812 --- [nio-8080-exec-1] c.e.designpattern.Decorator.Display      : /||*+---------------+*||/
2022-08-21 18:02:59.616  INFO 4812 --- [nio-8080-exec-1] c.e.designpattern.Decorator.Display      : /||*|你好,世界|*||/
2022-08-21 18:02:59.616  INFO 4812 --- [nio-8080-exec-1] c.e.designpattern.Decorator.Display      : /||*+---------------+*||/
2022-08-21 18:02:59.616  INFO 4812 --- [nio-8080-exec-1] c.e.designpattern.Decorator.Display      : /|+-------------------+|/
2022-08-21 18:02:59.616  INFO 4812 --- [nio-8080-exec-1] c.e.designpattern.Decorator.Display      : /+---------------------+/
0

评论区