OOP中的访问者模式及设计原则以

科技资讯 投稿 32900 0 评论

OOP中的访问者模式及设计原则以

一  设计原则 (SOLID

1.  S - 单一职责原则(Single Responsibllity Principle)

1.1  定义

1.2 举个栗子

TimeSheetReport 类, 依赖了 Employee 类, 遵循单一指责原则

2.  O - 开放关闭原则(Open-Closed Principle)

2.1 定义

  • 对扩展开放

模块对扩展开放,就意味着需求变化时,可以对模块扩展,使其具有满足那些改变的新行为

  • 对修改关闭

2.2 举个栗子

Order

Order原来的方法getShippingCost( , 违背了OCP

多态的思想,可以将 shipping 抽象成一个类, 后续新增运输方式, 无须修改Order 类原有的方法,
只需要在增加一个Shipping的派生类就可以了

3.  L - 里氏替换原则(Liskov Substitution Principle)

3.1 定义

  • 子类方法的参数类型应该比父类方法的参数类型更抽象或者说范围更广

  • 子类方法的返回值类型应该比父类方法的返回值类型更具体或者说范围更小

3.2 举个栗子

class Animal {}
class Cat extends Animal {
  faviroteFood: string;
  constructor(faviroteFood: string {
    super(;
    this.faviroteFood = faviroteFood;
  }
}

class Breeder {
  feed(c: Animal {
    console.log("Breeder feed animal";
  }
}

class CatCafe extends Breeder {
  feed(c: Animal {
    console.log("CatCafe feed animal";
  }
}

const animal = new Animal(;

const breeder = new Breeder(;
breeder.feed(animal;
// 约束子类能够接受父类入参
const catCafe = new CatCafe(;
catCafe.feed(animal;
  • 子类方法的返回值类型应该比父类方法的返回值类型更具体或者说范围更小

class Animal {}

class Cat extends Animal {
  faviroteFood: string;
  constructor(faviroteFood: string {
    super(;
    this.faviroteFood = faviroteFood;
  }
}

class Breeder {
  buy(: Animal {
    return new Animal(;
  }
}

class CatCafe extends Breeder {
  buy(: Cat {
    return new Cat("";
  }
}

const breeder = new Breeder(;
let a: Animal = breeder.buy(;

const catCafe = new CatCafe(;
a = catCafe.buy(;
    子类不应该强化前置条件
  • 子类不应该弱化后置条件

4.  I - 接口隔离原则(Interface Segregation Principle)

4.1 定义

最小的接口上

4.2 举个栗子

interface I {
  m1(: void;
  m2(: void;
  m3(: void;
  m4(: void;
  m5(: void;
}

class B implements I {
  m1(: void {}
  m2(: void {}
  m3(: void {}
  //实现的多余方法
  m4(: void {}
  //实现的多余方法
  m5(: void {}
}

class A {
  m1(i: I: void {
    i.m1(;
  }
  m2(i: I: void {
    i.m2(;
  }
  m3(i: I: void {
    i.m3(;
  }
}

class D implements I {
  m1(: void {}
  //实现的多余方法
  m2(: void {}
  //实现的多余方法
  m3(: void {}
  
  m4(: void {}
  m5(: void {}
}

class C {
  m1(i: I: void {
    i.m1(;
  }
  m4(i: I: void {
    i.m4(;
  }
  m5(i: I: void {
    i.m5(;
  }
}

将臃肿的接口 I 拆分为独立的几个接口,类 A 和类 C 分别与他们需要的接口建立依赖关系

interface I {
  m1(: void;
}

interface I2 {
  m2(: void;
  m3(: void;
}

interface I3 {
  m4(: void;
  m5(: void;
}

class B implements I, I2 {
  m1(: void {}
  m2(: void {}
  m3(: void {}
}

class A {
  m1(i: I: void {
    i.m1(;
  }
  m2(i: I2: void {
    i.m2(;
  }
  m3(i: I2: void {
    i.m3(;
  }
}

class D implements I, I3 {
  m1(: void {}
  m4(: void {}
  m5(: void {}
}

class C {
  m1(i: I: void {
    i.m1(;
  }
  m4(i: I3: void {
    i.m4(;
  }
  m5(i: I3: void {
    i.m5(;
  }
}

4.3 现实中的栗子

5.   D - 依赖倒置原则

5.1 定义

在软件设计中可以将类分为两个级别:高层模块, 低层模块, 高层模块不应该依赖低层模块,两者都应该依赖其抽象。高层模块指的是调用者,低层模块指的是一些基础操作

依赖倒置基于这个事实:相比于实现细节的多变性,抽象的内容要稳定的多

5.2 举个栗子

SoftwareProject类直接依赖了两个低级类, FrontendDeveloperBackendDeveloper, 而此时来了一个新的低层模块,就要修改 高层模块 SoftwareProject 的依赖

class FrontendDeveloper {
  public writeHtmlCode(: void {
    // some method
  }
}

class BackendDeveloper {
  public writeTypeScriptCode(: void {
    // some method
  }
}

class SoftwareProject {
  public frontendDeveloper: FrontendDeveloper;
  public backendDeveloper: BackendDeveloper;

  constructor( {
    this.frontendDeveloper = new FrontendDeveloper(;
    this.backendDeveloper = new BackendDeveloper(;
  }

  public createProject(: void {
    this.frontendDeveloper.writeHtmlCode(;
    this.backendDeveloper.writeTypeScriptCode(;
  }
}

可以遵循依赖倒置原则, 由于 FrontendDeveloper 和 BackendDeveloper是相似的类, 可以抽象出一个 develop 接口, 让FrontendDeveloperBackendDeveloper 去实现它, 我们不需要在 SoftwareProject类中以单一方式初始化 FrontendDeveloper 和 BackendDeveloper,而是将它们作为一个列表来遍历它们,分别调用每个 develop( 方法

interface Developer {
  develop(: void;
}

class FrontendDeveloper implements Developer {
  public develop(: void {
    this.writeHtmlCode(;
  }
  
  private writeHtmlCode(: void {
    // some method
  }
}

class BackendDeveloper implements Developer {
  public develop(: void {
    this.writeTypeScriptCode(;
  }
  
  private writeTypeScriptCode(: void {
    // some method
  }
}

class SoftwareProject {
  public developers: Developer[];
  
  public createProject(: void {
    this.developers.forEach((developer: Developer => {
      developer.develop(;
    };
  }
}

二  访问者模式 (Visitor Pattern

1.  意图

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

    Visitor
  • 的作用,即,也就是

  • Visitor
  • 是用于操作对象元素的

  • 也就是说,你可以只修Visitor 本身完成新操作的定义,而不需要修改原本对象, Visitor设计奇妙之处, 就是将对象的操作权移交给了 Visitor

2. 场景

  • 如果你需要对一个复杂对象结构 (例如对象树) 中的所有元素执行某些操作, 可使用访问者模式

  • 访问者模式通过在访问者对象中为多个目标类提供相同操作的变体,让你能在属于不同类的一组对象上执行同一操作

3.  访问者模式结构

    Visitor
  • :访问者接口

  • ConcreteVisitor:具体的访问者

  • Element: 可以被访问者使用的元素,它必须定义一个 Accept 属性,接收 visitor 对象。这是实现访问者模式的关键

class ConcreteElement implements Element {
  public accept(visitor: Visitor {
    visitor.visit(this
  }
}

从上面代码可以看出这样一条链路:Element 通过 accept函数接收到 Visitor 对象,并将自己的实例抛给 Visitor 的 visit函数,这样我们就可以在 Visitor 的 visit 方法中拿到对象实例,完成对对象的操作

4 . 实现方式以及伪代码

访问者模式为几何图像层次结构添加了对于 XML 文件导出功能的支持

4.1  在访问者接口中声明一组 “访问” 方法,分别对应程序中的每个具体元素类

interface Visitor {
  visitDot(d: Dot: void;
  visitCircle(c: Circle: void;
  visitRectangle(r: Rectangle: void;
}

4.2  声明元素接口。 如果程序中已有元素类层次接口,可在层次结构基类中添加抽象的 “接收” 方法。 该方法必须接受访问者对象作为参数

interface Shape {
  accept(v: Visitor: void;
}

4.3  在所有具体元素类中实现接收方法, 元素类只能通过访问者接口与访问者进行交互,不过访问者必须知晓所有的具体元素类,因为这些类在访问者方法中都被作为参数类型引用

class Dot implements Shape {
  public accept(v: Visitor: void {
   return v.visitDot(this
  }
}

class Circle implements Shape {
  public accept(v: Visitor: void {
   return v.visitCircle(this
  }
}

class Rectangle implements Shape {
  public accept(v: Visitor: void {
    return v.visitRectangle(this
  }
}

4.4 创建一个具体访问者类并实现所有的访问者方法

class XMLExportVisitor implements Visitor {
    visitDot(d: Dot: void {
      console.log(`导出点(dot)的 ID 和中心坐标`;
    }
    visitCircle(c: Circle: void {
      console.log(`导出圆(circle)的 ID 、中心坐标和半径`;
    }
    visitRectangle(r: Rectangle: void {
      console.log(`导出长方形(rectangle)的 ID 、左上角坐标、宽和长`;
    }
}

4.5  客户端必须创建访问者对象并通过 “接收” 方法将其传递给元素

const application = (shapes:Shape[],visitor:Visitor => {
  // ......
   for (const shape of  allShapes {
      shape.accept(visitor;
    }
  // ......
}
	
const allShapes = [
    new Dot(,
    new Circle(,
    new Rectangle(
];

const xmlExportVisitor = new XMLExportVisitor(;
application(allShapes, xmlExportVisitor;

4.6 完整代码预览

interface Visitor {
    visitDot(d: Dot: void;
    visitCircle(c: Circle: void;
    visitRectangle(r: Rectangle: void;
}

interface Shape {
   accept(v: Visitor: void;
}

class Dot implements Shape {
  public accept(v: Visitor: void {
     return v.visitDot(this
  }
}

class Circle implements Shape {
  public accept(v: Visitor: void {
    return v.visitCircle(this
  }
}

class Rectangle implements Shape {
  public accept(v: Visitor: void {
    return v.visitRectangle(this
  }
}

class XMLExportVisitor implements Visitor {
    visitDot(d: Dot: void {
      console.log(`导出点(dot)的 ID 和中心坐标`;
    }
    visitCircle(c: Circle: void {
      console.log(`导出圆(circle)的 ID 、中心坐标和半径`;
    }
    visitRectangle(r: Rectangle: void {
      console.log(`导出长方形(rectangle)的 ID 、左上角坐标、宽和长`;
    }
}

const allShapes = [
    new Dot(,
    new Circle(,
    new Rectangle(
];

const application = (shapes:Shape[],visitor:Visitor => {
  // ......
for (const shape of  allShapes {
    shape.accept(visitor;
  // .....
}
	
const xmlExportVisitor = new XMLExportVisitor(;
application(allShapes, xmlExportVisitor;

5. 访问者模式优缺点

优势:

  • 开闭原则。 你可以引入在不同类对象上执行的新行为,且无需对这些类做出修改

  • 单一职责原则 可将同一行为的不同版本移到同一个类中

不足:

  • 每次在元素层次结构中添加或移除一个类时, 你都要更新所有的访问者

  • 在访问者同某个元素进行交互时,它们可能没有访问元素私有成员变量和方法的必要权限

编程笔记 » OOP中的访问者模式及设计原则以

赞同 (32) or 分享 (0)
游客 发表我的评论   换个身份
取消评论

表情
(0)个小伙伴在吐槽