欢迎访问shiker.tech

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

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

行为型设计模式实战【四】-模板、访问者
(last modified Sep 8, 2022, 1:09 AM )
by
侧边栏壁纸
  • 累计撰写 176 篇文章
  • 累计创建 61 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

行为型设计模式实战【四】-模板、访问者

橙序员
2022-09-04 / 0 评论 / 0 点赞 / 367 阅读 / 3,798 字 / 正在检测百度是否收录... 正在检测必应是否收录...
文章摘要(AI生成)

模板方法模式定义定义一个操作中的算法的骨架,使得子类可以不改变一个算法的结构即可重新定义算法的某些拓展步骤角色AbstractClass(抽象类):AbstractClass角色不仅负责实现模板方法,还负责声明在模板方法中所使用到的抽象方法。这些抽象方法由子类ConcreteClass 角色负责实现

模板方法模式

定义

定义一个操作中的算法的骨架,使得子类可以不改变一个算法的结构即可重新定义算法的某些拓展步骤

优缺点

优点

  • 封装不变部分,扩展可变部分

    把认为是不变部分的算法封装到父类实现,而可变部分的则可以通过继承来继续扩展。

  • 提取公共部分代码,便于维护

    我们例子中刚刚走过的弯路就是最好的证明,如果我们不抽取到父类中,任由这种散乱 的代码发生,想想后果是什么样子?维护人员为了修正一个缺陷,需要到处查找类似的代码!

  • 行为由父类控制,子类实现

    基本方法是由子类实现的,因此子类可以通过扩展的方式增加相应的功能,符合开闭原则。

缺点

按照我们的设计习惯,抽象类负责声明最抽象、最一般的事物属性和方法,实现类完成 具体的事物属性和方法。但是模板方法模式却颠倒了,抽象类定义了部分抽象方法,由子类 实现,子类执行的结果影响了父类的结果,也就是子类对父类产生了影响,这在复杂的项目 中,会带来代码阅读的难度,而且也会让新手产生不适感。

使用场景

  • 多个子类有公有的方法,并且逻辑基本相同时。
  • 重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个 子类实现。
  • 重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通 过钩子函数(见“模板方法模式的扩展”)约束其行为

最佳实践

初级程序员在写程序的时候经常会问高手“父类怎么调用子类的方法”。那么父类是否可以调用子类的方法呢?该怎么调用呢?

● 把子类传递到父类的有参构造中,然后调用。

● 使用反射的方式调用,你使用了反射还有谁不能调用的?!

● 父类调用子类的静态方法。

这三种都是父类直接调用子类的方法,但是在项目中不允许使用。其实这个问题可以换个角度去理解,父类建立框架, 子类在重写了父类部分的方法后,再调用从父类继承的方法,产生不同的结果(而这正是模板方法模式)。这是不是也可以理解为父类调用了子类的方法呢?你修改了子类,影响了父类行为的结果,曲线救国的方式实现了父类依赖子类的场景,模板方法模式就是这种效果。

角色

  • AbstractClass(抽象类):AbstractClass角色不仅负责实现模板方法,还负责声明在模板方法中所使用到的抽象方法。这些抽象方法由子类ConcreteClass 角色负责实现。
  • ConcreteClass(具体类):该角色负责具体实现AbstractClass角色中定义的抽象方法。这里实现的方法将会在AbstractClass角色的模板方法中被调用。

image-20220904103836842

spring实现

我们来看一段将字符和字符串循环显示5次的简单程序。

抽象类

只实现了展示方法的抽象类

public abstract class AbstractDisplay {

   //子类要实现的抽象方法open
   public abstract void open();
   
   //交给子类去实现的抽象方法print
   public abstract void print();
   
   //交给子类去实现的抽象方法close
   public abstract void close();
   
   //本抽象类实现的display方法
   public final void display(){
      open();//先打开
      for(int i =0;i<5;i++){
         print();//循环打印
      }
      close();//关闭
   }
}

具体类

字符打印类:

@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
@Slf4j
public class CharItemDisplay extends AbstractDisplay {

	private char ch;

	public void initDisplay(char ch) {
		this.ch = ch;
	}

	@Override
	public void open() {//显示开始字符
		// TODO Auto-generated method stub
		log.info("<<");
	}

	@Override
	public void print() {//打印字符
		// TODO Auto-generated method stub
		log.info(String.valueOf(ch));
	}

	@Override
	public void close() {//显示结束字符
		// TODO Auto-generated method stub
		log.info(">>");
	}

}

字符串打印类:

@Component
@Slf4j
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class StringItemDisplay extends AbstractDisplay {

   private String string;
   private int width;

   public void initDisplay(String string) {
      this.string = string;
      this.width = string.getBytes().length;
   }

   @Override
   public void open() {
      // TODO Auto-generated method stub
      printLine();
   }

   @Override
   public void print() {
      // TODO Auto-generated method stub
      log.info("|" + string + "|");
   }

   @Override
   public void close() {
      // TODO Auto-generated method stub
      printLine();
   }

   private void printLine() {
      StringBuilder stringBuilder = new StringBuilder("+");
      for (int i = 0; i < width; i++) {
         stringBuilder.append("-");
      }
      stringBuilder.append("+");
      log.info(stringBuilder.toString());
   }

}

实例生成器

创建两个具体展示类的生成器

@Component
public abstract class TemplateGenerator {

   @Lookup
   protected abstract CharItemDisplay charDisplay();

   @Lookup
   protected abstract StringItemDisplay stringDisplay();

   public CharItemDisplay genCharDisplay(char ch){
      CharItemDisplay charItemDisplay = charDisplay();
      charItemDisplay.initDisplay(ch);
      return charItemDisplay;
   }

   public StringItemDisplay genStringDisplay(String string){
      StringItemDisplay stringItemDisplay = stringDisplay();
      stringItemDisplay.initDisplay(string);
      return stringItemDisplay;
   }
}

测试

@RestController
public class TemplateController {

   @Resource
   private TemplateGenerator templateGenerator;

   @GetMapping("template")
   public String template(){
      AbstractDisplay d1 = templateGenerator.genCharDisplay('H');
      AbstractDisplay d2 = templateGenerator.genStringDisplay("hello world");
      AbstractDisplay d3 = templateGenerator.genStringDisplay("世界你好");
      d1.display();
      d2.display();
      d3.display();
      return "SUCCESS";
   }
}

测试结果如下:

2022-09-04 10:55:17.651  INFO 5912 --- [nio-8080-exec-1] c.e.d.TempleteMethod.CharItemDisplay     : <<
2022-09-04 10:55:17.651  INFO 5912 --- [nio-8080-exec-1] c.e.d.TempleteMethod.CharItemDisplay     : H
2022-09-04 10:55:17.651  INFO 5912 --- [nio-8080-exec-1] c.e.d.TempleteMethod.CharItemDisplay     : H
2022-09-04 10:55:17.651  INFO 5912 --- [nio-8080-exec-1] c.e.d.TempleteMethod.CharItemDisplay     : H
2022-09-04 10:55:17.651  INFO 5912 --- [nio-8080-exec-1] c.e.d.TempleteMethod.CharItemDisplay     : H
2022-09-04 10:55:17.651  INFO 5912 --- [nio-8080-exec-1] c.e.d.TempleteMethod.CharItemDisplay     : H
2022-09-04 10:55:17.651  INFO 5912 --- [nio-8080-exec-1] c.e.d.TempleteMethod.CharItemDisplay     : >>
2022-09-04 10:55:17.651  INFO 5912 --- [nio-8080-exec-1] c.e.d.TempleteMethod.StringItemDisplay   : +-----------+
2022-09-04 10:55:17.651  INFO 5912 --- [nio-8080-exec-1] c.e.d.TempleteMethod.StringItemDisplay   : |hello world|
2022-09-04 10:55:17.651  INFO 5912 --- [nio-8080-exec-1] c.e.d.TempleteMethod.StringItemDisplay   : |hello world|
2022-09-04 10:55:17.651  INFO 5912 --- [nio-8080-exec-1] c.e.d.TempleteMethod.StringItemDisplay   : |hello world|
2022-09-04 10:55:17.651  INFO 5912 --- [nio-8080-exec-1] c.e.d.TempleteMethod.StringItemDisplay   : |hello world|
2022-09-04 10:55:17.651  INFO 5912 --- [nio-8080-exec-1] c.e.d.TempleteMethod.StringItemDisplay   : |hello world|
2022-09-04 10:55:17.651  INFO 5912 --- [nio-8080-exec-1] c.e.d.TempleteMethod.StringItemDisplay   : +-----------+
2022-09-04 10:55:17.651  INFO 5912 --- [nio-8080-exec-1] c.e.d.TempleteMethod.StringItemDisplay   : +------------+
2022-09-04 10:55:17.651  INFO 5912 --- [nio-8080-exec-1] c.e.d.TempleteMethod.StringItemDisplay   : |世界你好|
2022-09-04 10:55:17.651  INFO 5912 --- [nio-8080-exec-1] c.e.d.TempleteMethod.StringItemDisplay   : |世界你好|
2022-09-04 10:55:17.651  INFO 5912 --- [nio-8080-exec-1] c.e.d.TempleteMethod.StringItemDisplay   : |世界你好|
2022-09-04 10:55:17.651  INFO 5912 --- [nio-8080-exec-1] c.e.d.TempleteMethod.StringItemDisplay   : |世界你好|
2022-09-04 10:55:17.651  INFO 5912 --- [nio-8080-exec-1] c.e.d.TempleteMethod.StringItemDisplay   : |世界你好|
2022-09-04 10:55:17.651  INFO 5912 --- [nio-8080-exec-1] c.e.d.TempleteMethod.StringItemDisplay   : +------------+

访问者模式

定义

表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素的前提下定义作用于这些元素的新操作

优缺点

优点

  • 符合单一职责原则

    具体元素角色和访问者的职责非常明确地分离开来,各自演绎变化。

  • 优秀的扩展性

    由于职责分开,继续增加对数据的操作是非常快捷的

  • 灵活性非常高

缺点

  • 具体元素对访问者公布细节。访问者要访问一个类就必然要求这个类公布一些方法和数据,也就是说访问者关注了其 他类的内部细节,这是迪米特法则所不建议的。
  • 具体元素变更比较困难。具体元素角色的增加、删除、修改都是比较困难的,就上面那个例子,你想想,你要是 想增加一个成员变量,如年龄age,Visitor就需要修改,如果Visitor是一个还好办,多个呢? 业务逻辑再复杂点呢?
  • 违背了依赖倒置转原则。访问者依赖的是具体元素,而不是抽象元素,这破坏了依赖倒置原则,特别是在面向对 象的编程中,抛弃了对接口的依赖,而直接依赖实现类,扩展比较难。

使用场景

  • 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖 于其具体类的操作,也就说是用迭代器模式已经不能胜任的情景。
  • 需要对一个对象结构中的对象进行很多不同并且不相关的操作,而你想避免让这些操 作“污染”这些对象的类。

最佳实践

访问者模式是一种集中规整模式,特别适用于大规模重构的项目,在这一个阶段需求已 经非常清晰,原系统的功能点也已经明确,通过访问者模式可以很容易把一些功能进行梳 理,达到最终目的——功能集中化,如一个统一的报表运算、UI展现等,我们还可以与其他 模式混编建立一套自己的过滤器或者拦截器

角色

  • Visitor(访问者):Visitor角色负责对数据结构中每个具体的元素(ConcreteElement角色)声明一个用于访问XXxXx的visit (XXXXX)方法。visit ( XxXXX)是用于处理xxxxx的方法,负责实现该方法的是ConcreteVisitor角色
  • ConcreteVisitor (具体的访问者):ConcreteVisitor角色负责实现Visitor角色所定义的接口(API)。它要实现所有的visit(XXxxX)方法,即实现如何处理每个ConcreteElement角色
  • Element(元素):Element 角色表示Visitor角色的访问对象。它声明了接受访问者的accept方法。accept方法接收到的参数是Visitor角色。
  • ConcreteElement:ConcreteElement角色负责实现Element角色所定义的接口(API )。
  • ObjectStructure (对象结构):ObjectStructur角色负责处理Element角色的集合。ConcreteVisitor角色为每个Element角色都准备了处理方法。

image-20220904105813509

spring实现

我们使用观察者来访问文件和文件夹组成的数据结构,显示出相应的预览结果

访问者

表示访问者的抽象类,访问文件和文件夹

public abstract class Visitor {
   public abstract void visit(VFile VFile);
   public abstract void visit(VDirectory VDirectory);
}

具体访问者

用来显示文件和文件夹的预览结果

@Component
@Slf4j
public class ListVisitor extends Visitor {

   private String currentdir = "";
   @Override
   public void visit(VFile VFile) {
      // TODO Auto-generated method stub
        log.info(currentdir+"/"+ VFile);
   }

   @Override
   public void visit(VDirectory VDirectory) {
      // TODO Auto-generated method stub
        log.info(currentdir+"/"+ VDirectory);
        String savedir = currentdir;
        currentdir = currentdir+"/"+ VDirectory.getName();
        Iterator<Entry> it = VDirectory.iterator();
        while (it.hasNext()) {
         Entry entry = (Entry) it.next();
         entry.accept(this);
      }
        currentdir = savedir;
   }

}

元素

表示数据结构的接口,接受访问者的访问

public interface Element {
   void accept(Visitor v);
}

具体的元素

创建文件和文件夹的父类:

public abstract class Entry implements Element {

   public abstract String getName();
   public abstract int getSize();
   public Entry add(Entry entry)throws FileTreatmentException{
      throw new FileTreatmentException();
   }
   public String toString(){
      return getName()+" ("+getSize()+")";
   }
}

表示文件夹:

@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class VDirectory extends Entry {

   private String name;
   private ArrayList<Entry> dir = new ArrayList<Entry>();
   
   public void initDirectory(String name) {
      this.name = name;
   }
   
   public Entry add(Entry entry){
      dir.add(entry);
      return this;
   }
   
   public Iterator<Entry> iterator(){
      return dir.iterator();
   }
   
   @Override
   public void accept(Visitor v) {
      // TODO Auto-generated method stub
        v.visit(this);
   }

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

   @Override
   public int getSize() {
      // TODO Auto-generated method stub
      int size = 0;
      Iterator<Entry> it = dir.iterator();
      while (it.hasNext()) {
         Entry entry = (Entry) it.next();
         size+=entry.getSize();
      }
      return size;
   }

}

表示文件:

@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class VFile extends Entry {
    private String name;
    private int size;
    
   public void initFile(String name, int size) {
      this.name = name;
      this.size = size;
   }

   @Override
   public void accept(Visitor v) {
      // TODO Auto-generated method stub
        v.visit(this);
   }

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

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

}

对象结构

即我们在文件夹中实现的遍历方法:

public Iterator<Entry> iterator(){
    return dir.iterator();
 }

实例生成器

@Component
public abstract class VisitorGenerator {

   @Lookup
   protected abstract VDirectory directory();

   @Lookup
   protected abstract VFile file();

   public VDirectory genDirectory(String name){
      VDirectory directory = directory();
      directory.initDirectory(name);
      return directory;
   }

   public VFile genFile(String name, int size){
      VFile VFile = file();
      VFile.initFile(name, size);
      return VFile;
   }
}

测试

@RestController
public class VisitorController {

   @Resource
   VisitorGenerator visitorGenerator;
   @Resource
   ListVisitor listVisitor;

   @GetMapping("visitor")
   public String visitor(){
      try {
         System.out.println("Making root entries...");
         VDirectory rootdir = visitorGenerator.genDirectory("root");
         VDirectory bindir = visitorGenerator.genDirectory("bin");
         VDirectory tmpir = visitorGenerator.genDirectory("tmp");
         VDirectory usrdir = visitorGenerator.genDirectory("usr");
         rootdir.add(bindir);
         rootdir.add(tmpir);
         rootdir.add(usrdir);
         bindir.add(visitorGenerator.genFile("vi", 10000));
         bindir.add(visitorGenerator.genFile("latex", 20000));
         rootdir.accept(listVisitor);

         System.out.println("");
         System.out.println("Making user entries...");
         VDirectory yuki = visitorGenerator.genDirectory("yuki");
         VDirectory hanako = visitorGenerator.genDirectory("hanako");
         VDirectory tomura = visitorGenerator.genDirectory("tomura");
         usrdir.add(yuki);
         usrdir.add(hanako);
         usrdir.add(tomura);
         yuki.add(visitorGenerator.genFile("diary.html", 100));
         yuki.add(visitorGenerator.genFile("Composite.java", 200));
         hanako.add(visitorGenerator.genFile("memo.tex", 300));
         tomura.add(visitorGenerator.genFile("game.doc", 400));
         tomura.add(visitorGenerator.genFile("junk.mail", 500));
         rootdir.accept(listVisitor);

      } catch (FileTreatmentException e) {
         // TODO: handle exception
      }
      return "SUCCESS";
   }
}

测试结果如下:

Making root entries...
2022-09-04 11:07:54.561  INFO 5912 --- [nio-8080-exec-2] c.e.designpattern.Visitor.ListVisitor    : /root (30000)
2022-09-04 11:07:54.561  INFO 5912 --- [nio-8080-exec-2] c.e.designpattern.Visitor.ListVisitor    : /root/bin (30000)
2022-09-04 11:07:54.561  INFO 5912 --- [nio-8080-exec-2] c.e.designpattern.Visitor.ListVisitor    : /root/bin/vi (10000)
2022-09-04 11:07:54.561  INFO 5912 --- [nio-8080-exec-2] c.e.designpattern.Visitor.ListVisitor    : /root/bin/latex (20000)
2022-09-04 11:07:54.561  INFO 5912 --- [nio-8080-exec-2] c.e.designpattern.Visitor.ListVisitor    : /root/tmp (0)
2022-09-04 11:07:54.561  INFO 5912 --- [nio-8080-exec-2] c.e.designpattern.Visitor.ListVisitor    : /root/usr (0)

Making user entries...
2022-09-04 11:07:54.561  INFO 5912 --- [nio-8080-exec-2] c.e.designpattern.Visitor.ListVisitor    : /root (31500)
2022-09-04 11:07:54.561  INFO 5912 --- [nio-8080-exec-2] c.e.designpattern.Visitor.ListVisitor    : /root/bin (30000)
2022-09-04 11:07:54.562  INFO 5912 --- [nio-8080-exec-2] c.e.designpattern.Visitor.ListVisitor    : /root/bin/vi (10000)
2022-09-04 11:07:54.562  INFO 5912 --- [nio-8080-exec-2] c.e.designpattern.Visitor.ListVisitor    : /root/bin/latex (20000)
2022-09-04 11:07:54.562  INFO 5912 --- [nio-8080-exec-2] c.e.designpattern.Visitor.ListVisitor    : /root/tmp (0)
2022-09-04 11:07:54.562  INFO 5912 --- [nio-8080-exec-2] c.e.designpattern.Visitor.ListVisitor    : /root/usr (1500)
2022-09-04 11:07:54.562  INFO 5912 --- [nio-8080-exec-2] c.e.designpattern.Visitor.ListVisitor    : /root/usr/yuki (300)
2022-09-04 11:07:54.562  INFO 5912 --- [nio-8080-exec-2] c.e.designpattern.Visitor.ListVisitor    : /root/usr/yuki/diary.html (100)
2022-09-04 11:07:54.562  INFO 5912 --- [nio-8080-exec-2] c.e.designpattern.Visitor.ListVisitor    : /root/usr/yuki/Composite.java (200)
2022-09-04 11:07:54.562  INFO 5912 --- [nio-8080-exec-2] c.e.designpattern.Visitor.ListVisitor    : /root/usr/hanako (300)
2022-09-04 11:07:54.562  INFO 5912 --- [nio-8080-exec-2] c.e.designpattern.Visitor.ListVisitor    : /root/usr/hanako/memo.tex (300)
2022-09-04 11:07:54.562  INFO 5912 --- [nio-8080-exec-2] c.e.designpattern.Visitor.ListVisitor    : /root/usr/tomura (900)
2022-09-04 11:07:54.562  INFO 5912 --- [nio-8080-exec-2] c.e.designpattern.Visitor.ListVisitor    : /root/usr/tomura/game.doc (400)
2022-09-04 11:07:54.562  INFO 5912 --- [nio-8080-exec-2] c.e.designpattern.Visitor.ListVisitor    : /root/usr/tomura/junk.mail (500)
0

评论区