关于27种常见设计模式的总结

目录

六大原则

  1. 开闭原则(Open Close Principle)

开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。

  1. 里氏代换原则(Liskov Substitution Principle)

里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。

  1. 依赖倒转原则(Dependence Inversion Principle)

这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。

  1. 接口隔离原则(Interface Segregation Principle)

这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。

  1. 迪米特法则,又称最少知道原则(Demeter Principle)

最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。

  1. 合成复用原则(Composite Reuse Principle)

合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。

创建型模式

这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。

工厂模式

工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

适用场景

  1. 很多地方都需要创建某种对象时。
  2. 创建对象操作比较复杂,同时接口又需要统一时,同时又想要对外屏蔽时。
  3. 想要能够方便地扩展类型实现时。

模式缺点

  1. 工厂对于具体实现类型会产生依赖。
  2. 类型非常多时工厂类会变得非常复杂。

样例

public interface Shape {}  public class Circle implements Shape {}  public class Square implements Shape {}  public class ShapeFactory {      public Shape getShape1() { return new Circle(); }      public Shape getShape2() { return new Square(); }      public Shape getShape(String shape) {          switch (shape) {              case "circle": return new Circle();              case "square": return new Square();          }          return null;      }  }  

工厂方法模式

工厂方法(Factory Method)模式的意义是定义一个创建产品对象的工厂接口,将实际创建工作推迟到子类当中。核心工厂类不再负责产品的创建,这样核心类成为一个抽象工厂角色,仅负责具体工厂子类必须实现的接口,这样进一步抽象化的好处是使得工厂方法模式可以使系统在不修改具体工厂角色的情况下引进新的产品。

工厂方法模式是简单工厂模式的衍生,解决了许多简单工厂模式的问题。首先完全实现‘开-闭 原则’,实现了可扩展。其次更复杂的层次结构,可以应用于产品结果复杂的场合。

适用场景

  1. 同“工厂方法模式”
  2. 不希望每次增加一个类型时都需要修改工厂基类时。

模式缺点

  1. 代码结构变得更复杂了。
  2. 工厂也变得更多了。

样例

public interface Shape {}  public class Circle implements Shape {}  public class Square implements Shape {}  public interface ShapeAbstractFactory {      Shape getShape();  }  public class CircleFactory implements ShapeAbstractFactory {      public Shape getShape() { return new Circle(); }  }  public class SquareFactory implements ShapeAbstractFactory {      public Shape getShape() { return new Square(); }  }  

抽象工厂方法模式

抽象工厂模式(Abstract Factory Pattern)隶属于设计模式中的创建型模式,用于产品族的构建。抽象工厂是所有形态的工厂模式中最为抽象和最具一般性的一种形态。抽象工厂是指当有多个抽象角色时使用的一种工厂模式。抽象工厂模式可以向客户端提供一个接口,使客户端在不必指定产品的具体情况下,创建多个产品族中的产品对象。

工厂模式中的每一个形态都是针对一定问题的解决方案,工厂方法针对的是一类产品;而抽象工厂模式针对的是多类产品,一类产品内又有多种实现类型的情况。

适用场景

  1. 通常情况下产品需要成套使用。
  2. 每套解决方案内部又存在多种组合解决方案。

模式缺点

  1. 代码结构变得更复杂了。

样例

public interface Shape {}  public class Circle implements Shape {}  public class Square implements Shape {}  public interface color {}  public class Red implements Color {}  public class Blue implements Color {}  public interface AbstractFactory {      Shape getShape();      Red getColor();  }  public class Factory1 implements AbstractFactory { ... }  public class Factory2 implements AbstractFactory { ... }  

单例模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

注意:单例类只能有一个实例。

适用场景

  1. 逻辑相对简单的情况。
  2. 想要避免实例过多占用系统资源的情况。

模式缺点

  1. 修改单例对象的属性时,需要考虑多线程和高并发场景。

样例

public class Singleton {      private volatile static Singleton singleton;      private Singleton (){}  // 不允许通过new来实例化对象      public static Singleton getSingleton() {          if (singleton == null) {              synchronized (Singleton.class) {                  if (singleton == null) {                      singleton = new Singleton();                  }              }          }          return singleton;      }  }  

建造者模式

建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的。

适用场景

  1. 对象内部较为复杂,同时需要分离对象的创建与构建行为时。
  2. 一些内部基本部件相对稳定不变,而只是其组合经常变化的时候。
  3. 初始化一个对象时,参数过多,或者很多参数具有默认值时。

模式缺点

  1. 不适合创建差异性很大的产品类。
  2. 如内部变化复杂,会有很多的建造类。

样例

public interface Porudct { }  public interface Builder {      Product buildPart(Product p);  }  public interface Director {      Product getProduct();  }  

原型模式

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。

适用场景

  1. 当一个系统应该独立于它的产品创建,构成和表示时。
  2. 当要实例化的类是在运行时指定时,例如,通过动态装载。
  3. 为了避免一个与产品类层次平行的工厂类层次时。
  4. 当一个类的实例只能有几个不同状态组合中的一种时。创建相应数目的原型并克隆它们可能比每次用何时的状态手工实例化该类更方便一些。

模式缺点

  1. 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
  2. 必须实现Cloneable接口。

样例

public interface Cloneable {      Object clone();  }  

结构型模式

适配器模式

适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。

适用场景

  1. 可以让任何两个没有关联的类一起运行。
  2. 提高了类的复用。
  3. 增加了类的透明度。
  4. 灵活性好。

模式缺点

  1. 过多地使用适配器,会让系统非常零乱,不易整体进行把握。
  2. 由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。

样例

略,简单来说就是随意调用,类似于行为型地中介者模式。

桥接模式(称为嫁接更直观)

桥接(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化。这种类型的设计模式属于结构型模式,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。

这种模式涉及到一个作为桥接的接口,使得实体类的功能独立于接口实现类。这两种类型的类可被结构化改变而互不影响。

我们通过下面的实例来演示桥接模式(Bridge Pattern)的用法。其中,可以使用相同的抽象类方法但是不同的桥接实现类,来画出不同颜色的圆。

适用场景

  1. 想要避免在抽象与实现之间存在永久绑定。
  2. 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
  3. 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。
  4. 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。

模式缺点

  1. 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。

样例

略,简单来说就是将多种维度的属性从继承关系转化为组合关系。

过滤器模式

过滤器模式(Filter Pattern)或标准模式(Criteria Pattern)是一种设计模式,这种模式允许开发人员使用不同的标准来过滤一组对象,通过逻辑运算以解耦的方式把它们连接起来。这种类型的设计模式属于结构型模式,它结合多个标准来获得单一标准。

适用场景

  1. 设计人员将整个系统的输入输出行为理解为单个过滤器行为的叠加与组合。这样可以将问题分解,化繁为简。
  2. 任何两个过滤器,只要它们之间传送的数据遵守共同的规约就可以相连接。每个过滤器都有自己独立的输入输出接口,如果过滤器间传输的数据遵守其规约,只要用管道将它们连接就可以正常工作。
  3. 整个系统易于维护和升级:旧的过滤器可以被替代,新的过滤器可以添加到已有的系统上。软件的易于维护和升级是衡量软件系统质量的重要指标之一。在管道-过滤器模型中,只要遵守输入输出数据规约,任何一个过滤器都可以被另一个新的过滤器代替,同时为增强程序功能,可以添加新的过滤器。这样,系统的可维护性和可升级性得到了保证。
  4. 支持并发执行,每个过滤器作为一个单独的执行任务,可以与其它过滤器并发执行。过滤器的执行是独立的。不依赖于其它过滤器的。

模式缺点

  1. 效率较低。
  2. 过滤器如果组合情况复杂,那么会导致过滤器之间的结构变得复杂并且难以维护。

样例

public interface Filter {      List<Object> filter(List<Object> list);  }  

组合模式(树型模式更贴切)

组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。

这种模式创建了一个包含自己对象组的类。该类提供了修改相同对象组的方式。

适用场景

  1. 想表示对象的部分-整体层次结构(树形结构)。
  2. 希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。

模式缺点

  1. 暂无。

样例

public interface Node {      List<Node> getSubNodeList();  }  

装饰器模式

装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。

这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。

适用场景

  1. 动态地给一个对象添加一些额外的职责,但又不想增加很多子类的情况。
  2. 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

模式缺点

  1. 多层装饰比较复杂。

样例

public interface Shape { }  public interface ShapeDecorator {      void setShape(Shape shape);      void doSomething();  }  

外观模式

外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。

这种模式涉及到一个单一的类,该类提供了客户端请求的简化方法和对现有系统类方法的委托调用。

适用场景

  1. 为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

模式缺点

  1. 不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。

样例

public interface Shape { }  public interface ShapeMaker {      void doSomethingComplex();  }  

享元模式

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

享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。

适用场景

  1. 系统中有大量对象。
  2. 这些对象消耗大量内存。
  3. 这些对象的状态大部分可以外部化。
  4. 这些对象可以按照内蕴状态分为很多组,当把外蕴对象从对象中剔除出来时,每一组对象都可以用一个对象来代替。
  5. 系统不依赖于这些对象身份,这些对象是不可分辨的。

模式缺点

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

样例

略,简而言之就是单例的复杂情况。

代理模式

在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。

适用场景

  1. 想在访问一个类时做一些控制。

模式缺点

  1. 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
  2. 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

样例

略,简单来说就是替换类型并做一些外在包装。

行为型模式

责任链模式

顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。

在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。

适用场景

  1. 降低耦合度。它将请求的发送者和接收者解耦。
  2. 简化了对象。使得对象不需要知道链的结构。
  3. 增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。
  4. 增加新的请求处理类很方便。

模式缺点

  1. 不能保证请求一定被接收。
  2. 系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。
  3. 可能不容易观察运行时的特征,有碍于除错。

样例

public interface Chain {      Chain nextChain();      void doSomething();  }  

命令模式

命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。

适用场景

  1. 行为请求者与行为实现者需要相互分离。
  2. 需要容易地添加新命令。
  3. 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作时,也可以考虑使用命令模式。

模式缺点

  1. 使用命令模式可能会导致某些系统有过多的具体命令类。

样例

public interface Receiver { void doAction(); }  public interface Command { void execute(); }  public interface Invoker { void execute(); }  

解释器模式

解释器模式(Interpreter Pattern)提供了评估语言的语法或表达式的方式,它属于行为型模式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在 SQL 解析、符号处理引擎等。

适用场景

  1. 可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。
  2. 一些重复出现的问题可以用一种简单的语言来进行表达。
  3. 一个简单语法需要解释的场景。

模式缺点

  1. 可利用场景比较少。
  2. 对于复杂的文法比较难维护。
  3. 解释器模式会引起类膨胀。
  4. 解释器模式采用递归调用方法。

样例

public interface Expression { boolean interpret(String context); }  public class TerminalExpression implements Expression { …… }  public class AndExpression implements Expression { …… }  

迭代器模式

迭代器模式(Iterator Pattern)是 Java 和 .Net 编程环境中非常常用的设计模式。这种模式用于顺序访问集合对象的元素,不需要知道集合对象的底层表示。

适用场景

  1. 访问一个聚合对象的内容而无须暴露它的内部表示。
  2. 需要为聚合对象提供多种遍历方式。
  3. 为遍历不同的聚合结构提供一个统一的接口。

模式缺点

  1. 由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。

样例

public interface Iterator {      Obejct first();      boolean hasNext();      Object next();  }  public interface Aggregate { Iterator createIterator(); }  

中介者模式

中介者模式(Mediator Pattern)是用来降低多个对象和类之间的通信复杂性。这种模式提供了一个中介类,该类通常处理不同类之间的通信,并支持松耦合,使代码易于维护。中介者模式属于行为型模式。

适用场景

  1. 降低了类的复杂度,将一对多转化成了一对一。
  2. 各个类之间的解耦。
  3. 符合迪米特原则。

模式缺点

  1. 中介者会庞大,变得复杂难以维护。

样例

public interface Mediator { void doAllThings(); }  

备忘录模式

备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。

适用场景

  1. 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。
  2. 实现了信息的封装,使得用户不需要关心状态的保存细节。

模式缺点

  1. 消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。

样例

public interface Memento {      void setState(int state);      int getState();  }  public interface Originator {      void restore(Memento memento);      Memento createMemento();  }  public interface Caretaker {      Memento getMemento(int i);      void addMemento(Memento memento);  }  

观察者模式(或者叫发布-订阅模式)

当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。

适用场景

  1. 当一个抽象模型有两个方面,其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
  2. 当对一个对象的改变需要同时(通知)改变其他对象,而不知道具体有多少对象需要被改变。
  3. 当一个对象必须通知其他对象,而它又不能假定其他对象是谁。换言之,不希望这些对象是紧密耦合的。

模式缺点

  1. 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
  2. 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
  3. 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

样例

public interface Observer {      void update();  }  public interface Subject {      void attach(Observer o)      void detach(Observer o);      void notify();  }  

状态模式(或者叫发布-订阅模式)

在状态模式(State Pattern)中,类的行为是基于它的状态改变的。这种类型的设计模式属于行为型模式。在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。

适用场景

  1. 行为随状态改变而改变的场景。
  2. 条件、分支语句的代替者。

模式缺点

  1. 状态模式的使用必然会增加系统类和对象的个数。
  2. 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
  3. 状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。

样例

public interface State {     void handle();  }  public interface Context {      void setState(State s);      State getState();  }  

空对象模式

有两种含义:

  1. 在空对象模式(Null Object Pattern)中,一个空对象取代 NULL 对象实例的检查。
  2. Null 对象不是检查空值,而是反应一个不做任何动作的关系,这样的 Null 对象也可以在数据不可用的时候提供默认的行为。

在空对象模式中,我们创建一个指定各种要执行的操作的抽象类和扩展该类的实体类,还创建一个未对该类做任何实现的空对象类,该空对象类将无缝地使用在需要检查空值的地方。

适用场景

  1. 想要避免使用空指针(或null对象)的情况。
  2. 想要默认情况下具有“不做任何操作”的行为的情况。

模式缺点

  1. 必须手动实现isNil方法(或isNull方法)。

样例

// 含义1:取代null的检查  public interface Nullable {     boolean isNull();  }    // 含义2:不做任何动作的关系  public interface Command {      void handle();  }  public class NullCommand implements Command {      public void handle() {          return;      }  }  

策略模式

在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。

在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。

适用场景

  1. 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
  2. 一个系统需要动态地在几种算法中选择一种。
  3. 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。

模式缺点

  1. 策略类会增多。
  2. 所有策略类都需要对外暴露。

样例

public interface Strategy {     void strategy();  }  

模板模式

在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。

适用场景

  1. 有多个子类共有的方法,且逻辑相同。
  2. 重要的、复杂的方法,可以考虑作为模板方法。

模式缺点

  1. 每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。

样例

略,其实继承情况下,就是对某些方法提供默认实现,对某些方法允许重写覆盖。

访问者模式

在访问者模式(Visitor Pattern)中,我们使用了一个访问者类,它改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者改变而改变。这种类型的设计模式属于行为型模式。根据模式,元素对象已接受访问者对象,这样访问者对象就可以处理元素对象上的操作。

适用场景

  1. 对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。
  2. 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,也不希望在增加新操作时修改这些类。

模式缺点

  1. 具体元素对访问者公布细节,违反了迪米特原则。
  2. 具体元素变更比较困难。
  3. 违反了依赖倒置原则,依赖了具体类,没有依赖抽象。

样例

public interface Visitor {     void visit(Element e);  }  public interface Element {     void accept(Visitor v);  }  // 通常在accept中,调用v.visit(this)来进行访问操作。