java面试常见设计模式

  • 看这里,动画描述很好
  • 创建型模式
    • 工厂方法模式
      • 目的
      • 结构
      • 场景
      • 优缺点
      • 示例代码
        • button
        • factory
        • Demo.java: 客户端代码
    • 抽象工厂模式
      • 目的
      • 结构
      • 场景
      • 优缺点
      • 示例代码
        • buttons: 第一个产品层次结构
        • checkboxes: 第二个产品层次结构
        • factories
        • Demo
    • 生成器模式
      • 目的
      • 结构
      • 场景
      • 优缺点
      • 示例代码
        • builders
        • cars
        • components
        • director
        • Demo.java: 客户端代码
    • 原型模式
      • 目的
      • 结构
      • 场景
      • 优缺点
      • 示例代码
        • shapes: 形状列表
        • Demo.java: 克隆示例
    • 单例模式
      • 问题
      • 目的
      • 步骤
      • 结构
      • 场景
      • 优缺点
      • 示例代码
        • 基础单例(懒汉模式 延迟加载)(单线程)
        • 基础单例(懒汉模式 延迟加载)(多线程)
        • 采用延迟加载的线程安全单例
    • 其他
      • 工厂模式比较
        • 工厂
        • 构建方法
        • 静态构建 (或工厂) 方法
        • 简单工厂
        • 工厂方法
        • 抽象工厂
  • 结构型模式
    • 适配器模式
      • 运作方式
      • 目的
      • 结构
      • 场景
      • 优缺点
      • 示例代码
        • round
        • square
        • adapters
        • Demo.java: 客户端代码
    • 组合模式
      • 运作方式
      • 目的
      • 结构
      • 场景
      • 优缺点
      • 示例代码
        • shapes
        • editor
        • Demo.java: 客户端代码
    • 装饰模式
      • 问题
      • 运作方式
      • 目的
      • 结构
      • 场景
      • 优缺点
      • 示例代码
        • Source
        • decorators
        • Demo.java: 客户端代码
    • 外观模式
      • 问题
      • 运作方式
      • 目的
      • 结构
      • 场景
      • 优缺点
      • 示例代码
        • some_complex_media_library: 复杂视频转换程序库
        • facade
        • Demo.java: 客户端代码
    • 代理模式
      • 问题
      • 运作方式
      • 目的
      • 结构
      • 场景
      • 优缺点
      • 示例代码
  • @Deprecated
    • 单例模式
      • 懒汉式单例
      • 饿汉式单例
      • 区别
    • 工厂模式
      • 应用
    • 适配器模式
    • 代理模式

看这里,动画描述很好

设计模式

创建型模式

提供了创建对象的机制,提升已有代码的灵活性和可复用性

工厂方法模式


目的

生产不同类型的产品

针对不同类型的操作系统生成不同的 GUI 弹窗对象

结构

场景

无法预知对象确切类别及其依赖关系时, 可使用工厂方法。

工厂方法将创建产品的代码实际使用产品的代码分离, 从而能在不影响其他代码的情况下扩展产品创建部分代码。

例如, 如果需要向应用中添加一种新产品, 你只需要开发新的创建者子类, 然后重写其工厂方法即可。

优缺点

避免 创建者 和 具体产品 之间的  耦合
单一职责 原则。  可以将 产品创建代码 放在 程序的单一位置,使 代码更容易维护
开闭原则。 无需更改 现有客户端代码, 就可以 在程序中 引入 新的 产品类型
需要 引入 许多新的子类, 代码可能会因此变得更复杂

示例代码

生成跨平台的 GUI 元素

button

buttons/Button.java: 通用产品接口

package refactoring_guru.factory_method.example.buttons;/*** Common interface for all buttons.*/
public interface Button {void render();void onClick();
}

buttons/HtmlButton.java: 具体产品

package refactoring_guru.factory_method.example.buttons;/*** HTML button implementation.*/
public class HtmlButton implements Button {public void render() {System.out.println("<button>Test Button</button>");onClick();}public void onClick() {System.out.println("Click! Button says - 'Hello World!'");}
}

buttons/WindowsButton.java: 另一个具体产品

package refactoring_guru.factory_method.example.buttons;/*** Windows button implementation.*/
public class WindowsButton implements Button {...public void render() {...onClick();}public void onClick() {...}
}

factory

factory/Dialog.java: 基础创建者

package refactoring_guru.factory_method.example.factory;import refactoring_guru.factory_method.example.buttons.Button;/*** Base factory class. Note that "factory" is merely a role for the class. It* should have some core business logic which needs different products to be* created.*/
public abstract class Dialog {public void renderWindow() {// ... other code ...Button okButton = createButton();okButton.render();}/*** Subclasses will override this method in order to create specific button* objects.*/public abstract Button createButton();
}

factory/HtmlDialog.java: 具体创建者

package refactoring_guru.factory_method.example.factory;import refactoring_guru.factory_method.example.buttons.Button;
import refactoring_guru.factory_method.example.buttons.HtmlButton;/*** HTML Dialog will produce HTML buttons.*/
public class HtmlDialog extends Dialog {@Overridepublic Button createButton() {return new HtmlButton();}
}

factory/WindowsDialog.java: 另一个具体创建者

package refactoring_guru.factory_method.example.factory;import refactoring_guru.factory_method.example.buttons.Button;
import refactoring_guru.factory_method.example.buttons.WindowsButton;/*** Windows Dialog will produce Windows buttons.*/
public class WindowsDialog extends Dialog {@Overridepublic Button createButton() {return new WindowsButton();}
}

Demo.java: 客户端代码

package refactoring_guru.factory_method.example;import refactoring_guru.factory_method.example.factory.Dialog;
import refactoring_guru.factory_method.example.factory.HtmlDialog;
import refactoring_guru.factory_method.example.factory.WindowsDialog;/*** Demo class. Everything comes together here.*/
public class Demo {private static Dialog dialog;public static void main(String[] args) {configure();runBusinessLogic();}/*** The concrete factory is usually chosen depending on configuration or* environment options.*/static void configure() {if (System.getProperty("os.name").equals("Windows 10")) {dialog = new WindowsDialog();} else {dialog = new HtmlDialog();}}/*** All of the client code should work with factories and products through* abstract interfaces. This way it does not care which factory it works* with and what kind of product it returns.*/static void runBusinessLogic() {dialog.renderWindow();}
}

抽象工厂模式

一系列相关产品, 例如 椅子Chair 、 ​ 沙发Sofa和 咖啡桌Cof­fee­Table 。系列产品的不同变体。 例如, 你可以使用 现代Mod­ern 、 ​ 维多利亚Vic­to­ri­an 、 ​ 装饰风艺术Art­Deco等风格生成 椅子 、 ​ 沙发和 咖啡桌 。


设法生成同一系列每件家具对象, 这样才能确保其风格一致。 如果顾客收到的家具风格不一样, 他们可不会开心。



客户端无需了解工厂类, 也不用管工厂类创建出的椅子类型。 无论是现代风格, 还是维多利亚风格的椅子, 对于客户端来说没有分别, 它只需要同一系列的桌椅就可以了。此外, 无论工厂返回的是何种椅子变体, 它都会和由同一工厂对象创建的沙发或咖啡桌风格一致

目的

创建一系列相关的对象

针对不同类型的操作系统生成不同 系列GUI 弹窗对象

结构

场景

需要与多个不同系列的相关产品交互, 但是由于无法提前获取相关信息, 或者出于对未来扩展性的考虑, 你不希望代码基于产品的具体类进行构建, 在这种情况下, 你可以使用抽象工厂。

抽象工厂为你提供了一个接口, 可用于创建每个系列产品的对象。 只要代码通过该接口创建对象, 那么你就不会生成与应用程序已生成的产品类型不一致的产品

在设计良好的程序中, 每个类仅负责一件事。 如果一个类与多种类型产品交互, 就可以考虑将工厂方法抽取到独立的工厂类具备完整功能的抽象工厂类中。

优缺点

可以确保 同一工厂 生成的产品 相互匹配
可以避免 客户端 和 具体产品 代码的耦合
单一职责原则。 可以将产品 生成代码 抽取到同一位置, 使得代码易于维护
开闭原则。 向应用程序中引入新产品变体时, 无需修改客户端代码。
由于采用该模式需要向应用中引入众多接口和类, 代码可能会比之前更加复杂。

示例代码

buttons: 第一个产品层次结构

buttons/Button.java

public interface Button {void paint();
}

buttons/MacOSButton.java

public class MacOSButton implements Button {@Overridepublic void paint() {System.out.println("You have created MacOSButton.");}
}

buttons/WindowsButton.java

public class WindowsButton implements Button {@Overridepublic void paint() {System.out.println("You have created WindowsButton.");}
}

checkboxes: 第二个产品层次结构

checkboxes/Checkbox.java

public interface Checkbox {void paint();
}

checkboxes/MacOSCheckbox.java

public class MacOSCheckbox implements Checkbox {@Overridepublic void paint() {System.out.println("You have created MacOSCheckbox.");}
}

checkboxes/WindowsCheckbox.java

public class WindowsCheckbox implements Checkbox {@Overridepublic void paint() {System.out.println("You have created WindowsCheckbox.");}
}

factories

factories/GUIFactory.java: 抽象工厂

public interface GUIFactory {Button createButton();Checkbox createCheckbox();
}

factories/MacOSFactory.java: 具体工厂 ( mac­OS)

public class MacOSFactory implements GUIFactory {@Overridepublic Button createButton() {return new MacOSButton();}@Overridepublic Checkbox createCheckbox() {return new MacOSCheckbox();}
}

factories/WindowsFactory.java: 具体工厂 (Win­dows)

public class WindowsFactory implements GUIFactory {@Overridepublic Button createButton() {return new WindowsButton();}@Overridepublic Checkbox createCheckbox() {return new WindowsCheckbox();}
}

Demo

app/Application.java: 客户端代码

public class Application {private Button button;private Checkbox checkbox;public Application(GUIFactory factory) {button = factory.createButton();checkbox = factory.createCheckbox();}public void paint() {button.paint();checkbox.paint();}
}

Demo.java: 程序配置

public class Demo {/*** Application picks the factory type and creates it in run time (usually at* initialization stage), depending on the configuration or environment* variables.*/private static Application configureApplication() {Application app;GUIFactory factory;String osName = System.getProperty("os.name").toLowerCase();if (osName.contains("mac")) {factory = new MacOSFactory();app = new Application(factory);} else {factory = new WindowsFactory();app = new Application(factory);}return app;}public static void main(String[] args) {Application app = configureApplication();app.paint();}
}

生成器模式

使你能够分步骤创建复杂对象。 该模式允许你使用相同的创建代码生成不同类型和形式的对象。



建造一栋简单的房屋, 首先你需要建造四面墙和地板安装房门和一套窗户, 然后再建造一个屋顶。 但是如果你想要一栋更宽敞更明亮的房屋, 还要有院子和其他设施 (例如暖气、 排水和供电设备), 那又该怎么办呢?

最简单的方法是扩展 房屋基类, 然后创建一系列涵盖所有参数组合的子类。 但最终你将面对相当数量的子类。 任何新增的参数 (例如门廊类型) 都会让这个层次结构更加复杂

另一种方法则无需生成子类。 你可以在 房屋基类中创建一个包括所有可能参数的超级构造函数, 并用它来控制房屋对象。 这种方法确实可以避免生成子类, 但它却会造成另外一个问题

生成器模式建议将对象构造代码从产品类中抽取出来, 并将其放在一个名为生成器的独立对象中

创建多个不同的生成器, 用不同方式实现一组相同的创建步骤。 然后你就可以在创建过程中使用这些生成器 (例如按顺序调用多个构造步骤) 来生成不同类型的对象

第一个建造者使用木头和玻璃制造房屋, 第二个建造者使用石头和钢铁, 而第三个建造者使用黄金和钻石。 在调用同一组步骤后, 第一个建造者会给你一栋普通房屋, 第二个会给你一座小城堡, 而第三个则会给你一座宫殿

你可以进一步将用于创建产品的一系列生成器步骤调用抽取成为单独的主管类主管类定义创建步骤的执行顺序, 而生成器则提供这些步骤的实现

目的

根据不同的属性需要,使用相同的步骤,创造拥有不同属性的对象
分步骤地 制造 不同型号的汽车, 使用 相同的生产过程 制造不同类型的产品 (汽车手册)

结构


场景

1. 避免重叠构造函数 tele­scop­ing con­struc­tor

假设你的构造函数中有十个可选参数, 那么调用该函数会非常不方便; 因此, 你需要重载这个构造函数, 新建几个只有较少参数的简化版。 但这些构造函数仍需调用主构造函数, 传递一些默认数值来替代省略掉的参数

class Pizza {Pizza(int size) { ... }Pizza(int size, boolean cheese) { ... }Pizza(int size, boolean cheese, boolean pepperoni) { ... }// ...

生成器模式让你可以分步骤生成对象, 而且允许你仅使用必须的步骤。 应用该模式后, 你再也不需要将几十个参数塞进构造函数里了

2. 当你希望使用代码创建不同形式的产品 (例如石头或木头房屋) 时, 可使用生成器模式

如果你需要创建的各种形式的产品, 它们的制造过程相似仅有细节上的差异, 此时可使用生成器模式。

基本生成器接口中定义了所有可能的制造步骤具体生成器实现这些步骤来制造特定形式的产品。 同时, 主管类将负责管理制造步骤的顺序

优缺点

可以 分步创建 对象, 暂缓创建步骤 或 递归运行创建步骤。
生成 不同形式的产品 时, 可以 复用相同 的 制造代码。
单一职责 原则。 可以将 复杂构造代码 从 产品的 业务逻辑 中分离出来。
由于该模式需要新增多个类, 因此 代码 整体复杂程度 会有所增加

示例代码

分步骤地制造不同型号的汽车, 使用相同的生产过程制造不同类型的产品 (汽车手册)。

主管 控制着 构造顺序。 它知道 制造 各种汽车型号 需要 调用 的 生产步骤 它仅与 汽车的 通用接口进行交互。 这样就能 将不同类型的生成器 传递给主管了最终结果 将 从生成器对象中 获得, 因为 主管 不知道 最终产品的类型只有 生成器对象知道 自己生成的产品 是什么

builders

builders/Builder.java: 通用生成器接口

public interface Builder {void setCarType(CarType type);void setEngine(Engine engine);void setTripComputer(TripComputer tripComputer);void setGPSNavigator(GPSNavigator gpsNavigator);
}

builders/CarBuilder.java: 汽车生成器

public class CarBuilder implements Builder {private CarType type;private Engine engine;private TripComputer tripComputer;private GPSNavigator gpsNavigator;@Overridepublic void setCarType(CarType type) {this.type = type;}
... public Car getResult() {return new Car(type, seats, engine, transmission, tripComputer, gpsNavigator);}
}

builders/CarManualBuilder.java: 汽车手册生成器

public class CarManualBuilder implements Builder{private CarType type;private Engine engine;private TripComputer tripComputer;private GPSNavigator gpsNavigator;@Overridepublic void setCarType(CarType type) {this.type = type;}
...public Manual getResult() {return new Manual(type, seats, engine, transmission, tripComputer, gpsNavigator);}
}

cars

cars/Car.java: 汽车产品

public class Car {private final CarType carType;private final Engine engine;private final TripComputer tripComputer;private final GPSNavigator gpsNavigator;private double fuel = 0;public Car(CarType carType, Engine engine, TripComputer tripComputer, GPSNavigator gpsNavigator) {this.carType = carType;this.engine = engine;this.tripComputer = tripComputer;if (this.tripComputer != null) {this.tripComputer.setCar(this);}this.gpsNavigator = gpsNavigator;}public CarType getCarType() {return carType;}
...
}

cars/Manual.java: 手册产品

public class Manual {private final CarType carType;private final Engine engine;private final TripComputer tripComputer;private final GPSNavigator gpsNavigator;public Manual(CarType carType, Engine engine, TripComputer tripComputer, GPSNavigator gpsNavigator) {this.carType = carType;this.engine = engine;this.tripComputer = tripComputer;this.gpsNavigator = gpsNavigator;}public String print() {String info = "";info += "Type of car: " + carType + "\n";info += "Engine: volume - " + engine.getVolume() + "; mileage - " + engine.getMileage() + "\n";if (this.tripComputer != null) {info += "Trip Computer: Functional" + "\n";} else {info += "Trip Computer: N/A" + "\n";}if (this.gpsNavigator != null) {info += "GPS Navigator: Functional" + "\n";} else {info += "GPS Navigator: N/A" + "\n";}return info;}
}

cars/CarType.java

public enum CarType {CITY_CAR, SPORTS_CAR, SUV
}

components

components/Engine.java: 产品特征 1

public class Engine {private final double volume;private double mileage;private boolean started;public Engine(double volume, double mileage) {this.volume = volume;this.mileage = mileage;}public void on() {started = true;}public void off() {started = false;}public boolean isStarted() {return started;}public void go(double mileage) {if (started) {this.mileage += mileage;} else {System.err.println("Cannot go(), you must start engine first!");}}public double getVolume() {return volume;}public double getMileage() {return mileage;}
}

components/GPSNavigator.java: 产品特征 2

public class GPSNavigator {private String route;public GPSNavigator() {this.route = "221b, Baker Street, London  to Scotland Yard, 8-10 Broadway, London";}public GPSNavigator(String manualRoute) {this.route = manualRoute;}public String getRoute() {return route;}
}

…其他产品特征

director

director/Director.java: 主管控制生成器

public class Director {public void constructSportsCar(Builder builder) {builder.setCarType(CarType.SPORTS_CAR);builder.setEngine(new Engine(3.0, 0));builder.setTripComputer(new TripComputer());builder.setGPSNavigator(new GPSNavigator());}public void constructCityCar(Builder builder) {builder.setCarType(CarType.CITY_CAR);builder.setEngine(new Engine(1.2, 0));builder.setTripComputer(new TripComputer());builder.setGPSNavigator(new GPSNavigator());}public void constructSUV(Builder builder) {builder.setCarType(CarType.SUV);builder.setEngine(new Engine(2.5, 0));builder.setGPSNavigator(new GPSNavigator());}
}

Demo.java: 客户端代码

public class Demo {public static void main(String[] args) {Director director = new Director();// Director gets the concrete builder object from the client// (application code). That's because application knows better which// builder to use to get a specific product.CarBuilder builder = new CarBuilder();director.constructSportsCar(builder);// The final product is often retrieved from a builder object, since// Director is not aware and not dependent on concrete builders and// products.Car car = builder.getResult();System.out.println("Car built:\n" + car.getCarType());CarManualBuilder manualBuilder = new CarManualBuilder();// Director may know several building recipes.director.constructSportsCar(manualBuilder);Manual carManual = manualBuilder.getResult();System.out.println("\nCar manual built:\n" + carManual.print());}}

原型模式


如果你有一个对象, 并希望生成与其完全相同的一个复制品, 你该如何实现呢? 首先, 你必须新建一个属于相同类的对象。 然后, 你必须遍历原始对象的所有成员变量, 并将成员变量值复制到新对象中

不错! 但有个小问题。 并非所有对象都能通过这种方式进行复制, 因为有些对象可能拥有私有成员变量, 它们在对象本身以外是不可见的

直接复制 必须知道 对象所属的类 才能创建复制品, 所以代码必须依赖该类。
即使 可以接受 额外的依赖性, 那还有另外一个问题: 有时 只知道对象所实现的接口, 而不知道其所属的具体类,比如可向方法的某个参数传入实现了某个接口的任何对象。

目的

所有的原型类必须有一个通用的接口, 使得即使在对象所属的具体类 未知的情况下也能复制对象原型对象可以生成自身的完整副本, 因为相同类的对象可以相互访问对方的私有成员变量

能够复制已有对象, 而又无需使代码依赖它们所属的类。
产品在得到 大规模生产前 会使用原型进行各种测试。 但在这种情况下, 原型只是一种 被动的工具, 不参与任何真正的生产活动工业原型 并不是真正意义上的自我复制, 因此细胞有丝分裂 (还记得生物学知识吗?) 或许是更恰当的类比。
有丝分裂会产生一对完全相同的细胞。 原始细胞就是一个原型, 它在复制体的生成过程中起到了推动作用。

结构

场景

如果你需要复制一些对象, 同时又希望代码独立于这些对象所属的具体类, 可以使用原型模式。

原型模式为 客户端代码 提供一个 通用接口, 客户端代码 可通过这一接口与 所有 实现了克隆的对象 进行交互
它也使得 客户端代码 与 其所克隆的对象 具体类独立开来。

如果子类的区别仅在于其对象的初始化方式, 那么你可以使用该模式来减少子类的数量。 别人创建这些子类的目的可能是为了创建特定类型的对象。

在原型模式中, 你可以使用一系列预生成的、 各种类型的对象作为原型。客户端不必根据需求对子类进行实例化, 只需找到合适的原型并对其进行克隆即可

优缺点

你可以克隆对象, 而 无需 与它们所属的 具体类 相耦合
你可以 克隆 预生成原型, 避免反复运行 初始化代码
你可以 更方便地 生成复杂对象。
你可以用 继承以外 的方式来 处理复杂对象的不同配置
克隆包含循环引用的复杂对象可能会非常麻烦。

示例代码

shapes: 形状列表

shapes/Shape.java: 通用形状接口

public abstract class Shape {public int x;public int y;public String color;public Shape() {}public Shape(Shape target) {if (target != null) {this.x = target.x;this.y = target.y;this.color = target.color;}}public abstract Shape clone();@Overridepublic boolean equals(Object object2) {if (!(object2 instanceof Shape)) return false;Shape shape2 = (Shape) object2;return shape2.x == x && shape2.y == y && Objects.equals(shape2.color, color);}
}

shapes/Circle.java: 简单形状

public class Circle extends Shape {public int radius;public Circle() {}public Circle(Circle target) {super(target);if (target != null) {this.radius = target.radius;}}@Overridepublic Shape clone() {return new Circle(this);}@Overridepublic boolean equals(Object object2) {if (!(object2 instanceof Circle) || !super.equals(object2)) return false;Circle shape2 = (Circle) object2;return shape2.radius == radius;}
}

shapes/Rectangle.java: 另一个形状

public class Rectangle extends Shape {public int width;public int height;public Rectangle() {}public Rectangle(Rectangle target) {super(target);if (target != null) {this.width = target.width;this.height = target.height;}}@Overridepublic Shape clone() {return new Rectangle(this);}@Overridepublic boolean equals(Object object2) {if (!(object2 instanceof Rectangle) || !super.equals(object2)) return false;Rectangle shape2 = (Rectangle) object2;return shape2.width == width && shape2.height == height;}
}

Demo.java: 克隆示例

public class Demo {public static void main(String[] args) {List<Shape> shapes = new ArrayList<>();List<Shape> shapesCopy = new ArrayList<>();Circle circle = new Circle();circle.x = 10;circle.y = 20;circle.radius = 15;circle.color = "red";shapes.add(circle);Circle anotherCircle = (Circle) circle.clone();shapes.add(anotherCircle);Rectangle rectangle = new Rectangle();rectangle.width = 10;rectangle.height = 20;rectangle.color = "blue";shapes.add(rectangle);cloneAndCompare(shapes, shapesCopy);}private static void cloneAndCompare(List<Shape> shapes, List<Shape> shapesCopy) {for (Shape shape : shapes) {shapesCopy.add(shape.clone());}for (int i = 0; i < shapes.size(); i++) {if (shapes.get(i) != shapesCopy.get(i)) {System.out.println(i + ": Shapes are different objects (yay!)");if (shapes.get(i).equals(shapesCopy.get(i))) {System.out.println(i + ": And they are identical (yay!)");} else {System.out.println(i + ": But they are not identical (booo!)");}} else {System.out.println(i + ": Shape objects are the same (booo!)");}}}
}

单例模式

保证 一个类 只有 一个实例, 并提供 一个访问 该实例 的 全局节点

问题

单例模式同时解决了两个问题, 所以违反了单一职责原则

职责1. 保证一个类只有一个实例

为什么会想要控制一个类所拥有的实例数量? 最常见的原因是控制某些共享资源 (例如数据库或文件) 的访问权限。如果你创建了一个对象, 同时过一会儿后你决定再创建一个新对象, 此时你会获得之前已创建的对象, 而不是一个新对象。而普通构造函数无法实现上述行为, 因为构造函数的设计决定了它必须总是返回一个新对象。


职责2. 为该实例提供一个全局访问节点

全局变量 在使用上十分方便, 但 同时也非常不安全因为任何代码 都 有可能覆盖掉那些变量的内容, 从而引发程序崩溃。
和全局变量一样, 单例模式 也 允许在程序的 任何地方 访问特定对象但是它可以保护该实例不被其他代码覆盖。还有一点: 你不会希望解决同一个问题的代码 分散 在程序各处 的因此更好的方式是将 其放在同一个类 中, 特别是当 其他代码 已经依赖这个类时

目的

保证 一个类 只有 一个实例, 并提供 一个访问 该实例 的 全局节点

政府是单例模式的一个很好的示例。 一个国家只有一个官方政府。 不管组成政府的每个人的身份是什么, ​ “某政府” 这一称谓总是鉴别那些掌权者的全局访问节点

步骤

1-默认构造函数设为私有防止 其他对象 使用单例类的 new运算符
2- 新建一个静态构建方法 作为构造函数。 该函数会 “偷偷调用私有构造函数创建对象, 并将其保存在一个静态成员变量中。 此后所有对于该函数的调用 都将 返回这一缓存对象

如果代码能够 访问单例类, 那它就能 调用 单例类 的 静态方法
无论何时 调用该方法, 它总是会 返回相同的对象

结构

场景

场景 1- 如果程序中的某个类对于所有客户端 只有一个可用的实例, 可以使用单例模式。

单例模式 禁止 通过除 特殊构建方法以外的 任何方式 来创建 自身类 的对象
该方法可以 创建一个新对象 , 但如果 该对象 已经 被创建, 则 返回 已有的对象

场景 2- 如果你需要更加严格地控制全局变量, 可以使用单例模式。

单例模式与全局变量不同, 它 保证类 只存在一个实例 除了单例类自己以外, 无法通过任何方式替换缓存的实例。

请注意,可以调整限制设定生成单例实例的数量, 只需修改 获取实例方法, 即 getInstance 中的代码即可实现。

优缺点

可以保证 一个类 只有一个实例
获得了一个 指向该实例的 全局访问节点
仅在 首次请求单例对象时 对其进行初始化
违反了 单一职责 原则。 该模式 同时解决了两个问题
单例模式可能 掩盖 不良设计, 比如 程序 各组件之间 相互了解过多等
该模式在 多线程环境 下需要进行 特殊处理, 避免 多个线程 多次 创建单例对象

示例代码

基础单例(懒汉模式 延迟加载)(单线程)

仅需隐藏构造函数并实现一个静态的构建方法

Singleton.java: 单例

public final class Singleton {private static Singleton instance;public String value;private Singleton(String value) {// The following code emulates slow initialization.try {Thread.sleep(1000);} catch (InterruptedException ex) {ex.printStackTrace();}this.value = value;}public static Singleton getInstance(String value) {if (instance == null) {instance = new Singleton(value);}return instance;}
}
public class DemoSingleThread {public static void main(String[] args) {System.out.println("If you see the same value, then singleton was reused (yay!)" + "\n" +"If you see different values, then 2 singletons were created (booo!!)" + "\n\n" +"RESULT:" + "\n");Singleton singleton = Singleton.getInstance("FOO");Singleton anotherSingleton = Singleton.getInstance("BAR");System.out.println(singleton.value);System.out.println(anotherSingleton.value);}
}

基础单例(懒汉模式 延迟加载)(多线程)

相同的类在多线程环境中会出错。 多线程可能会同时调用构建方法并获取多个单例类的实例

public final class Singleton {private static Singleton instance;public String value;private Singleton(String value) {// The following code emulates slow initialization.try {Thread.sleep(1000);} catch (InterruptedException ex) {ex.printStackTrace();}this.value = value;}public static Singleton getInstance(String value) {if (instance == null) {instance = new Singleton(value);}return instance;}
}
public class DemoMultiThread {public static void main(String[] args) {System.out.println("If you see the same value, then singleton was reused (yay!)" + "\n" +"If you see different values, then 2 singletons were created (booo!!)" + "\n\n" +"RESULT:" + "\n");Thread threadFoo = new Thread(new ThreadFoo());Thread threadBar = new Thread(new ThreadBar());threadFoo.start();threadBar.start();}static class ThreadFoo implements Runnable {@Overridepublic void run() {Singleton singleton = Singleton.getInstance("FOO");System.out.println(singleton.value);}}static class ThreadBar implements Runnable {@Overridepublic void run() {Singleton singleton = Singleton.getInstance("BAR");System.out.println(singleton.value);}}
}

采用延迟加载的线程安全单例

创建首个单例对象时对线程进行同步

public final class Singleton {private static volatile Singleton instance;public String value;private Singleton(String value) {this.value = value;}public static Singleton getInstance(String value) {Singleton result = instance;if (result == null) {synchronized(Singleton.class) {if (instance == null) {instance = new Singleton(value);}}return result ;}
}
public class DemoMultiThread {public static void main(String[] args) {System.out.println("If you see the same value, then singleton was reused (yay!)" + "\n" +"If you see different values, then 2 singletons were created (booo!!)" + "\n\n" +"RESULT:" + "\n");Thread threadFoo = new Thread(new ThreadFoo());Thread threadBar = new Thread(new ThreadBar());threadFoo.start();threadBar.start();}static class ThreadFoo implements Runnable {@Overridepublic void run() {Singleton singleton = Singleton.getInstance("FOO");System.out.println(singleton.value);}}static class ThreadBar implements Runnable {@Overridepublic void run() {Singleton singleton = Singleton.getInstance("BAR");System.out.println(singleton.value);}}
}

其他

工厂模式比较

工厂

创建一些东西的函数、 方法或类。
最常见的情况下, 工厂创建的是对象。 但是它们也可以创建文件和数据库记录等其他东西例如
创建程序 GUI 的函数或方法;
创建用户的类;
以特定方式调用类构造函数的静态方法。

构建方法

 《重构与模式》 中被定义为 创建对象的方法这意味着每个工厂方法模式的结果都是 “构建方法”, 但反过来则并非如此

在实际中, 构建方法只是构造函数调用的封装器。 它可能只是一个能更好地表达意图的名称。 此外, 它可以让代码独立于构造函数的改动, 甚至还可以包含一些特殊的逻辑, 返回已有对象 而不是创建新对象。

class Number {private $value;public function __construct($value) {$this->value = $value;}public function next() {return new Number ($this->value + 1);}
}

静态构建 (或工厂) 方法

被声明为 sta­t­ic 的 构建方法。 换句话说, 无需创建对象 就能在 某个类 上 调用该方法
当 静态构建方法 返回一个 新对象 时, 它就成为了 构造函数的替代品
class User {private $id, $name, $email, $phone;public function __construct($id, $name, $email, $phone) {$this->id = $id;$this->name = $name;$this->email = $email;$this->phone = $phone;}public static function load($id) {list($id, $name, $email, $phone) = DB::load_data('users', 'id', 'name', 'email', 'phone');$user = new User($id, $name, $email, $phone);return $user;}
}

简单工厂

描述了一个类, 它拥有一个包含 大量条件语句 的 构建方法
可根据 方法的参数 来选择 对 何种产品 进行 初始化并将其返回。

简单工厂是引入工厂方法或抽象工厂模式时的一个中间步骤

简单工厂通常没有子类

顺便提一句, 如果将一个简单工厂声明为 abstract类型, 它并不会神奇地变成抽象工厂模式

class UserFactory {public static function create($type) {switch ($type) {case 'user': return new User();case 'customer': return new Customer();case 'admin': return new Admin();default:throw new Exception('传递的用户类型错误。');}}
}

工厂方法

是一种创建型设计模式, 其在 父类中 提供一个 创建对象 的方法, 允许 子类 决定实例化对象 的类型
abstract class Department {public abstract function createEmployee($id);public function fire($id) {$employee = $this->createEmployee($id);$employee->paySalary();$employee->dismiss();}
}class ITDepartment extends Department {public function createEmployee($id) {return new Programmer($id);}
}class AccountingDepartment extends Department {public function createEmployee($id) {return new Accountant($id);}
}

抽象工厂

创建  !一系列相关! 或 !相互依赖! 的对象, 而无需指定其具体类
什么是 “系列对象”? 例如有这样一组的对象: ​ 运输工具+ 引擎+ 控制器 。 它可能会有几个变体:
汽车+ 内燃机+ 方向盘
飞机+ 喷气式发动机+ 操纵杆

程序中不涉及产品 系列的话, 那就不需要抽象工厂

结构型模式

对象和类 组装成较大的结构, 并同时保持结构的灵活和高效

适配器模式


假如你正在开发一款股票市场监测程序, 它会从不同来源下载 XML 格式的股票数据, 然后向用户呈现出美观的图表

在开发过程中, 你决定在程序中整合一个第三方智能分析函数库。 但是遇到了一个问题, 那就是分析函数库只兼容 JSON 格式的数据


你可以修改程序库来支持 XML。 但是, 这可能需要修改部分依赖该程序库的现有代码。 甚至还有更糟糕的情况, 你可能根本没有程序库的源代码, 从而无法对其进行修改

运作方式

适配器 实现 与 其中一个 现有对象 兼容的接口
现有对象 可以 使用该接口 安全地调用 适配器方法
适配器方法 被调用后 将 以另一个对象兼容的 格式 和 顺序 将请求传递给该对象
有时你甚至可以 创建一个 双向适配器 来实现双向转换调用

目的

一种结构型设计模式, 它能 使 接口不兼容 的对象 能够 相互合作
是一个特殊的对象, 能够 转换对象接口, 使其能 与其他对象进行交互
适配器模式 通过 封装对象 将 复杂的转换过程 隐藏于幕后,被封装的对象甚至察觉不到适配器的存在
适配器模式在 Java 代码中很常见。
基于一些 遗留代码的系统 常常会使用该模式。 适配器让 遗留代码 与 现代的类 得以相互合作
如果你是第一次从美国到欧洲旅行, 那么在给笔记本充电时可能会大吃一惊。
不同国家的电源插头和插座标准不同。
美国插头和德国插座不匹配。
同时提供美国标准插座和欧洲标准插头的电源适配器可以解决你的难题

结构

对象适配器

适配器实现了其中一个对象的接口, 并对另一个对象进行封装


类适配器

 适配器同时继承两个对象的接口。 请注意, 这种方式仅能在支持多重继承的编程语言中实现, 例如 C++。

场景

当你希望使用某个类, 但是其接口与其他代码不兼容时, 可以使用适配器类

适配器模式允许你创建一个 中间层类
其可作为 代码 与 遗留类、 第三方类 或 提供怪异接口的类 之间 的转换器

如果您需要复用这样一些类, 他们处于同一个继承体系, 并且他们又有了额外的一些共同的方法, 但是这些共同的方法不是所有在这一继承体系中的子类所具有的共性

你可以扩展每个子类, 将缺少的功能添加到新的子类中。
但是, 你必须在所有新子类中重复添加这些代码, 这样会使得代码有坏味道。
将 缺失功能 添加到 一个适配器类中 是一种优雅得多的解决方案
然后你可以将 缺少功能的对象 封装在适配器中, 从而 动态地获取 所需功能
如要这一点正常运作, 目标类 必须 要有通用接口, 适配器的 成员变量 应当遵循该通用接口。
这种方式同装饰模式非常相似。

优缺点

单一职责原则 你可以将 接口 或 数据转换代码 从 程序主要业务 逻辑中 分离
开闭原则  只要 客户端代码 通过 客户端接口 与 适配器 进行交互, 你就能在 不修改 现有客户端代码的情况下在程序中添加新类型的适配器。
代码整体复杂度增加, 因为你需要新增一系列接口和类。 有时直接更改服务类使其与其他代码兼容会更简单。

示例代码

让方钉适配圆孔,不兼容的对象相互合作

round

round/RoundHole.java: 圆孔

public class RoundHole {private double radius;public RoundHole(double radius) {this.radius = radius;}public double getRadius() {return radius;}public boolean fits(RoundPeg peg) {boolean result;result = (this.getRadius() >= peg.getRadius());return result;}
}

round/RoundPeg.java: 圆钉

public class RoundPeg {private double radius;public RoundPeg() {}public RoundPeg(double radius) {this.radius = radius;}public double getRadius() {return radius;}
}

square

square/SquarePeg.java: 方钉

public class SquarePeg {private double width;public SquarePeg(double width) {this.width = width;}public double getWidth() {return width;}public double getSquare() {double result;result = Math.pow(this.width, 2);return result;}
}

adapters

adapters/SquarePegAdapter.java: 方钉到圆孔的适配器

public class SquarePegAdapter extends RoundPeg {private SquarePeg peg;public SquarePegAdapter(SquarePeg peg) {this.peg = peg;}@Overridepublic double getRadius() {double result;// Calculate a minimum circle radius, which can fit this peg.result = (Math.sqrt(Math.pow((peg.getWidth() / 2), 2) * 2));return result;}
}

Demo.java: 客户端代码

public class Demo {public static void main(String[] args) {// Round fits round, no surprise.RoundHole hole = new RoundHole(5);RoundPeg rpeg = new RoundPeg(5);if (hole.fits(rpeg)) {System.out.println("Round peg r5 fits round hole r5.");}SquarePeg smallSqPeg = new SquarePeg(2);SquarePeg largeSqPeg = new SquarePeg(20);// hole.fits(smallSqPeg); // Won't compile.// Adapter solves the problem.SquarePegAdapter smallSqPegAdapter = new SquarePegAdapter(smallSqPeg);SquarePegAdapter largeSqPegAdapter = new SquarePegAdapter(largeSqPeg);if (hole.fits(smallSqPegAdapter)) {System.out.println("Square peg w2 fits round hole r5.");}if (!hole.fits(largeSqPegAdapter)) {System.out.println("Square peg w20 does not fit into round hole r5.");}}
}

组合模式


你有两类对象: ​ 产品和 盒子一个盒子中可以包含多个 产品或者几个较小的 盒子 。 这些小 盒子中同样可以包含一些 产品或更小的 盒子 , 以此类推。

假设你希望在这些类的基础上开发一个定购系统。 订单中可以包含无包装的简单产品, 也可以包含装满产品的盒子…… 以及其他盒子。 此时你会如何计算每张订单的总价格

现实中,可以打开所有盒子, 找到每件产品, 然后计算总价。 这在真实世界中或许可行, 但在程序中, 你并不能简单地使用循环语句来完成该工作。 你必须事先知道所有 产品和 盒子的类别所有盒子的嵌套层数以及其他繁杂的细节信息

运作方式

组合模式建议使用一个通用接口与 产品和 盒子进行交互, 并且在该接口中声明一个计算总价的方法

那么方法该如何设计呢? 对于一个产品该方法直接返回其价格; 对于一个盒子, 该方法遍历盒子中的所有项目询问每个项目的价格, 然后返回该盒子的总价格。 如果其中某个项目是小一号的盒子, 那么当前盒子也会遍历其中的所有项目, 以此类推, 直到计算出所有内部组成部分的价格。 你甚至可以在盒子的最终价格中增加额外费用, 作为该盒子的包装费用

最大优点在于你无需了解 构成树状结构的对象的具体类
无需了解对象是 简单的产品 还是 复杂的盒子。
你只需 调用 通用接口 以 相同的方式 对其进行处理即可。 当你调用该方法后, 对象会将请求沿着树结构传递下去

目的

使用它将对象组合成树状结构, 并且能像使用独立对象一样使用它们
大部分 国家的军队 都采用 层次结构管理
每支部队包括 几个师, 师由旅 构成, 旅由团构成, 团可以继续划分为排
最后, 每个排 由一小队 实实在在的 士兵组成军事命令由最高层下达, 通过每个层级传递, 直到每位士兵都知道自己应该服从的命令

结构

场景

如果你需要实现树状对象结构, 可以使用组合模式。

组合模式为你提供了两种共享公共接口的基本元素类型: 简单叶节点和复杂容器。
容器中可以包含 叶节点 和 其他容器 。 这使得你可以构建 树状嵌套递归对象结构

如果你希望客户端代码以相同方式处理简单和复杂元素, 可以使用该模式。

组合模式中定义的所有元素共用同一个接口。
在这一接口的帮助下, 客户端不必在意其所使用的对象的具体类。

优缺点

你可以利用 多态 和 递归机制 更方便地使用复杂树结构。
开闭原则。 无需更改现有代码, 你就可以在应用中添加新元素, 使其成为对象树的一部分。
对于功能差异较大的类, 提供公共接口或许会有困难。
在特定情况下, 你需要过度一般化组件接口, 使其变得令人难以理解。

示例代码

简单和复合图形

shapes

shapes/Shape.java: 通用形状接口

public interface Shape {int getX();int getY();int getWidth();int getHeight();void move(int x, int y);boolean isInsideBounds(int x, int y);void select();void unSelect();boolean isSelected();void paint(Graphics graphics);
}

shapes/BaseShape.java: 提供基本功能的抽象形状

abstract class BaseShape implements Shape {public int x;public int y;public Color color;private boolean selected = false;BaseShape(int x, int y, Color color) {this.x = x;this.y = y;this.color = color;}@Overridepublic int getX() {return x;}@Overridepublic int getY() {return y;}@Overridepublic int getWidth() {return 0;}@Overridepublic int getHeight() {return 0;}@Overridepublic void move(int x, int y) {this.x += x;this.y += y;}@Overridepublic boolean isInsideBounds(int x, int y) {return x > getX() && x < (getX() + getWidth()) &&y > getY() && y < (getY() + getHeight());}@Overridepublic void select() {selected = true;}@Overridepublic void unSelect() {selected = false;}@Overridepublic boolean isSelected() {return selected;}void enableSelectionStyle(Graphics graphics) {graphics.setColor(Color.LIGHT_GRAY);Graphics2D g2 = (Graphics2D) graphics;float dash1[] = {2.0f};g2.setStroke(new BasicStroke(1.0f,BasicStroke.CAP_BUTT,BasicStroke.JOIN_MITER,2.0f, dash1, 0.0f));}void disableSelectionStyle(Graphics graphics) {graphics.setColor(color);Graphics2D g2 = (Graphics2D) graphics;g2.setStroke(new BasicStroke());}@Overridepublic void paint(Graphics graphics) {if (isSelected()) {enableSelectionStyle(graphics);}else {disableSelectionStyle(graphics);}// ...}
}

shapes/Dot.java: 点

public class Dot extends BaseShape {private final int DOT_SIZE = 3;public Dot(int x, int y, Color color) {super(x, y, color);}@Overridepublic int getWidth() {return DOT_SIZE;}@Overridepublic int getHeight() {return DOT_SIZE;}@Overridepublic void paint(Graphics graphics) {super.paint(graphics);graphics.fillRect(x - 1, y - 1, getWidth(), getHeight());}
}

shapes/Circle.java: 圆形

public class Circle extends BaseShape {public int radius;public Circle(int x, int y, int radius, Color color) {super(x, y, color);this.radius = radius;}@Overridepublic int getWidth() {return radius * 2;}@Overridepublic int getHeight() {return radius * 2;}public void paint(Graphics graphics) {super.paint(graphics);graphics.drawOval(x, y, getWidth() - 1, getHeight() - 1);}
}

shapes/Rectangle.java: 三角形

public class Rectangle extends BaseShape {public int width;public int height;public Rectangle(int x, int y, int width, int height, Color color) {super(x, y, color);this.width = width;this.height = height;}@Overridepublic int getWidth() {return width;}@Overridepublic int getHeight() {return height;}@Overridepublic void paint(Graphics graphics) {super.paint(graphics);graphics.drawRect(x, y, getWidth() - 1, getHeight() - 1);}
}

shapes/CompoundShape.java: 由其他形状对象组成的复合形状

public class CompoundShape extends BaseShape {protected List<Shape> children = new ArrayList<>();public CompoundShape(Shape... components) {super(0, 0, Color.BLACK);add(components);}public void add(Shape component) {children.add(component);}public void add(Shape... components) {children.addAll(Arrays.asList(components));}public void remove(Shape child) {children.remove(child);}public void remove(Shape... components) {children.removeAll(Arrays.asList(components));}public void clear() {children.clear();}...@Overridepublic void paint(Graphics graphics) {if (isSelected()) {enableSelectionStyle(graphics);graphics.drawRect(getX() - 1, getY() - 1, getWidth() + 1, getHeight() + 1);disableSelectionStyle(graphics);}for (refactoring_guru.composite.example.shapes.Shape child : children) {child.paint(graphics);}}
}

editor

editor/ImageEditor.java: 形状编辑器

public class ImageEditor {private EditorCanvas canvas;private CompoundShape allShapes = new CompoundShape();public ImageEditor() {canvas = new EditorCanvas();}public void loadShapes(Shape... shapes) {allShapes.clear();allShapes.add(shapes);canvas.refresh();}private class EditorCanvas extends Canvas {JFrame frame;private static final int PADDING = 10;EditorCanvas() {createFrame();refresh();addMouseListener(new MouseAdapter() {@Overridepublic void mousePressed(MouseEvent e) {allShapes.unSelect();allShapes.selectChildAt(e.getX(), e.getY());e.getComponent().repaint();}});}void createFrame() {frame = new JFrame();frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);frame.setLocationRelativeTo(null);JPanel contentPanel = new JPanel();Border padding = BorderFactory.createEmptyBorder(PADDING, PADDING, PADDING, PADDING);contentPanel.setBorder(padding);frame.setContentPane(contentPanel);frame.add(this);frame.setVisible(true);frame.getContentPane().setBackground(Color.LIGHT_GRAY);}public int getWidth() {return allShapes.getX() + allShapes.getWidth() + PADDING;}public int getHeight() {return allShapes.getY() + allShapes.getHeight() + PADDING;}void refresh() {this.setSize(getWidth(), getHeight());frame.pack();}public void paint(Graphics graphics) {allShapes.paint(graphics);}}
}

Demo.java: 客户端代码

public class Demo {public static void main(String[] args) {ImageEditor editor = new ImageEditor();editor.loadShapes(new Circle(10, 10, 10, Color.BLUE),new CompoundShape(new Circle(110, 110, 50, Color.RED),new Dot(160, 160, Color.RED)),new CompoundShape(new Rectangle(250, 250, 100, 100, Color.GREEN),new Dot(240, 240, Color.GREEN),new Dot(240, 360, Color.GREEN),new Dot(360, 360, Color.GREEN),new Dot(360, 240, Color.GREEN)));}
}

装饰模式

问题

假设你正在开发一个提供通知功能的库其他程序可使用它向用户发送关于重要事件的通知

库的最初版本基于 通知器Noti­fi­er类, 其中只有很少的几个成员变量一个构造函数一个 send发送方法。 该方法可以接收来自客户端的消息参数, 并将该消息发送给一系列的邮箱邮箱列表则是通过构造函数传递给通知器的。 作为客户端的第三方程序仅会创建和配置通知器对象一次, 然后在有重要事件发生时对其进行调用

此后某个时刻, 你会发现库的用户希望使用除邮件通知之外的功能。 许多用户会希望接收关于紧急事件的手机短信, 还有些用户希望在微信上接收消息, 而公司用户则希望在 QQ 上接收消息

这有什么难的呢? 首先扩展 通知器类, 然后在新的子类中加入额外的通知方法。 现在客户端要对所需通知形式的对应类进行初始化, 然后使用该类发送后续所有的通知消息

但是很快有人会问: ​ “为什么不同时使用多种通知形式呢? 如果房子着火了, 你大概会想在所有渠道中都收到相同的消息吧。”

你可以尝试创建一个特殊子类来将多种通知方法组合在一起以解决该问题。 但这种方式会使得代码量迅速膨胀, 不仅仅是程序库代码, 客户端代码也会如此。

运作方式

当你需要更改一个对象的行为时, 第一个跳入脑海的想法就是扩展它所属的类。 但是, 你不能忽视继承可能引发的几个严重问题

继承是静态的。 你无法 在运行时 更改 已有对象的行为 , 只能使用 由不同子类创建的对象 来替代 当前的整个对象。
子类 只能有 一个父类。 大部分编程语言 不允许一个类同时 继承多个类 的行为。

其中一种方法是用聚合或组合 , 而不是继承。 两者的工作方式几乎一模一样: 一个对象包含指向另一个对象的引用, 并将部分工作委派给引用对象继承中的对象继承了父类的行为, 它们自己能够完成这些工作。

一个对象可以使用多个类的行为包含多个指向其他对象的引用, 并将各种工作委派给引用对象。 聚合 (或组合) 组合是许多设计模式背后的关键原则 (包括装饰在内)。 记住这一点后, 让我们继续关于模式的讨论。

在消息通知示例中, 我们可以将简单邮件通知行为放在基类 通知器中, 但将所有其他通知方法放入装饰中


由于所有的装饰实现了与通知基类相同的接口, 客户端的其他代码不在意自己到底是与 “纯粹” 的通知器对象, 还是与装饰后的通知器对象进行交互

我们可以使用相同方法来完成其他行为 (例如设置消息格式或者创建接收人列表)。 只要所有装饰都遵循相同的接口, 客户端就可以使用任意自定义的装饰来装饰对象

目的

通过将对象放入 包含行为 的 特殊封装对象 中来为 原对象绑定 新的行为
穿衣服 是使用装饰的一个例子。
觉得冷时, 你可以穿一件毛衣。 如果穿毛衣还觉得冷, 你可以再套上一件夹克。 如果遇到下雨, 你还可以再穿一件雨衣。所有这些衣物都 “扩展” 了你的基本行为, 但它们并不是你的一部分, 如果你不再需要某件衣物, 可以方便地随时脱掉。

结构

场景

如果你希望在无需修改代码的情况下即可使用对象, 且希望在运行时为对象新增额外的行为, 可以使用装饰模式

装饰能将业务逻辑组织为层次结构, 你可为各层创建一个装饰, 在运行时将各种不同逻辑组合成对象。
由于这些对象都遵循通用接口, 客户端代码能以相同的方式使用这些对象。

如果用继承来扩展对象行为的方案难以实现或者根本不可行, 你可以使用该模式。

许多编程语言使用 final 最终关键字来 限制对某个类的进一步扩展
复用最终类 已有行为 的唯一方法 是使用装饰模式: 用封装器对其进行封装。

优缺点

你无需 创建 新子类 即可 扩展对象的行为
你可以 在运行时 添加或删除对象 的功能。
你可以用 多个装饰封装对象 来 组合几种行为
单一职责原则。 你可以将实现了 许多不同行为的一个大类 拆分为多个较小的类
在封装器 栈中 删除特定封装器 比较困难。
实现行为 不受装饰栈 顺序影响 的装饰 比较困难。
各层的初始化 配置代码 看上去可能会很糟糕。

示例代码

编码和压缩装饰

最初的业务逻辑类仅能读取和写入 纯文本的数据。 此后, 我们创建了几个小的封装器类, 以便在执行标准操作后添加新的行为

第一个封装器负责加密和解密数据, 而第二个则负责压缩和解压数据

你甚至可以让这些封装器嵌套封装将它们组合起来

Source

decorators/DataSource.java: 定义了读取和写入操作的通用数据接口

public interface DataSource {void writeData(String data);String readData();
}

decorators/FileDataSource.java: 简单数据读写器

public class FileDataSource implements DataSource {private String name;public FileDataSource(String name) {this.name = name;}@Overridepublic void writeData(String data) {File file = new File(name);try (OutputStream fos = new FileOutputStream(file)) {fos.write(data.getBytes(), 0, data.length());} catch (IOException ex) {System.out.println(ex.getMessage());}}@Overridepublic String readData() {char[] buffer = null;File file = new File(name);try (FileReader reader = new FileReader(file)) {buffer = new char[(int) file.length()];reader.read(buffer);} catch (IOException ex) {System.out.println(ex.getMessage());}return new String(buffer);}
}

decorators

decorators/DataSourceDecorator.java: 抽象基础装饰

public class DataSourceDecorator implements DataSource {private DataSource wrappee;DataSourceDecorator(DataSource source) {this.wrappee = source;}@Overridepublic void writeData(String data) {wrappee.writeData(data);}@Overridepublic String readData() {return wrappee.readData();}
}

decorators/EncryptionDecorator.java: 加密装饰

public class EncryptionDecorator extends DataSourceDecorator {public EncryptionDecorator(DataSource source) {super(source);}@Overridepublic void writeData(String data) {super.writeData(encode(data));}@Overridepublic String readData() {return decode(super.readData());}private String encode(String data) {byte[] result = data.getBytes();for (int i = 0; i < result.length; i++) {result[i] += (byte) 1;}return Base64.getEncoder().encodeToString(result);}private String decode(String data) {byte[] result = Base64.getDecoder().decode(data);for (int i = 0; i < result.length; i++) {result[i] -= (byte) 1;}return new String(result);}
}

decorators/CompressionDecorator.java: 压缩装饰


public class CompressionDecorator extends DataSourceDecorator {private int compLevel = 6;public CompressionDecorator(DataSource source) {super(source);}public int getCompressionLevel() {return compLevel;}public void setCompressionLevel(int value) {compLevel = value;}@Overridepublic void writeData(String data) {super.writeData(compress(data));}@Overridepublic String readData() {return decompress(super.readData());}private String compress(String stringData) {...}private String decompress(String stringData) {...}}
}

Demo.java: 客户端代码

public class Demo {public static void main(String[] args) {String salaryRecords = "Name,Salary\nJohn Smith,100000\nSteven Jobs,912000";DataSourceDecorator encoded = new CompressionDecorator(new EncryptionDecorator(new FileDataSource("out/OutputDemo.txt")));encoded.writeData(salaryRecords);DataSource plain = new FileDataSource("out/OutputDemo.txt");System.out.println("- Input ----------------");System.out.println(salaryRecords);System.out.println("- Encoded --------------");System.out.println(plain.readData());System.out.println("- Decoded --------------");System.out.println(encoded.readData());}
}

外观模式

问题

假设你必须在代码中使用某个复杂的库或框架中的众多对象。
正常情况下, 你需要负责所有对象的初始化工作、 管理其依赖关系并按正确的顺序执行方法等

运作方式

外观类为包含许多活动部件的复杂子系统 提供一个简单的接口。 与直接调用子系统相比外观提供的功能可能比较有限, 但它却包含了客户端真正关心的功能

如果你的程序需要与包含几十种功能的复杂库整合, 但只需使用其中非常少的功能, 那么使用外观模式会非常方便

例如, 上传猫咪搞笑短视频到社交媒体网站的应用可能会用到专业的视频转换库, 但它只需使用一个包含 encode­(file­name, for­mat)方法 (以文件名与文件格式为参数进行编码的方法) 的类即可。 在创建这个类并将其连接到视频转换库后, 你就拥有了自己的第一个外观

目的

能为程序库、 框架或其他复杂类提供一个简单的接口
当你通过 电话 给 商店下达订单时, 接线员 就是 该商店 的 所有服务 和 部门的 外观
接线员为你 提供了 一个同购物系统、 支付网关 和 各种送货服务 进行互动的简单语音接口

结构

场景

如果你需要一个指向复杂子系统的直接接口, 且该接口的功能有限, 则可以使用外观模式

子系统通常会随着时间的推进变得越来越复杂。 即便是应用了设计模式, 通常你也会创建更多的类。
尽管在多种情形中子系统可能是更灵活或易于复用的, 但其所需的配置和样板代码数量将会增长得更快。
为了解决这个问题, 外观将会提供指向子系统中最常用功能的快捷方式, 能够满足客户端的大部分需求。

如果需要将子系统组织为多层结构, 可以使用外观

创建外观来定义子系统中各层次的入口。 你可以要求子系统仅使用外观来进行交互, 以减少子系统之间的耦合。让我们回到视频转换框架的例子。 该框架可以拆分为两个层次: 音频相关和视频相关。
你可以为每个层次创建一个外观, 然后要求各层的类必须通过这些外观进行交互。 这种方式看上去与中介者模式非常相似。

优缺点

你可以让自己的代码 独立于 复杂子系统。
 外观可能成为与程序中所有类都耦合的上帝对象。

示例代码

复杂视频转换库的简单接口

some_complex_media_library: 复杂视频转换程序库

some_complex_media_library/VideoFile.java

public class VideoFile {private String name;private String codecType;public VideoFile(String name) {this.name = name;this.codecType = name.substring(name.indexOf(".") + 1);}public String getCodecType() {return codecType;}public String getName() {return name;}
}

some_complex_media_library/Codec.java

public interface Codec {}

some_complex_media_library/MPEG4CompressionCodec.java

public class MPEG4CompressionCodec implements Codec {public String type = "mp4";}

some_complex_media_library/OggCompressionCodec.java

public class OggCompressionCodec implements Codec {public String type = "ogg";
}

some_complex_media_library/CodecFactory.java

public class CodecFactory {public static Codec extract(VideoFile file) {String type = file.getCodecType();if (type.equals("mp4")) {System.out.println("CodecFactory: extracting mpeg audio...");return new MPEG4CompressionCodec();}else {System.out.println("CodecFactory: extracting ogg audio...");return new OggCompressionCodec();}}
}

facade

facade/VideoConversionFacade.java: 外观提供了进行视频转换的简单接口

public class VideoConversionFacade {public File convertVideo(String fileName, String format) {System.out.println("VideoConversionFacade: conversion started.");VideoFile file = new VideoFile(fileName);Codec sourceCodec = CodecFactory.extract(file);Codec destinationCodec;if (format.equals("mp4")) {destinationCodec = new MPEG4CompressionCodec();} else {destinationCodec = new OggCompressionCodec();}VideoFile buffer = BitrateReader.read(file, sourceCodec);VideoFile intermediateResult = BitrateReader.convert(buffer, destinationCodec);File result = (new AudioMixer()).fix(intermediateResult);System.out.println("VideoConversionFacade: conversion completed.");return result;}
}

Demo.java: 客户端代码

public class Demo {public static void main(String[] args) {VideoConversionFacade converter = new VideoConversionFacade();File mp4Video = converter.convertVideo("youtubevideo.ogg", "mp4");// ...}
}

代理模式

问题

为什么要控制对于某个对象的访问呢? 举个例子: 有这样一个消耗大量系统资源的巨型对象, 你只是偶尔需要使用它, 并非总是需要

你可以实现延迟初始化: 在实际有需要时再创建该对象。 对象的所有客户端都要执行延迟初始代码。 不幸的是, 这很可能会带来很多重复代码

在理想情况下, 我们希望将代码直接放入对象的类中, 但这并非总是能实现: 比如类可能是第三方封闭库的一部分。

运作方式

代理模式建议新建一个与原服务对象接口相同的代理类, 然后更新应用以将代理对象传递给所有原始对象客户端。 代理类接收到客户端请求后会创建实际的服务对象, 并将所有工作委派给它

这有什么好处呢? 如果需要在类的主要业务逻辑前后执行一些工作, 你无需修改类就能完成这项工作。 由于代理实现的接口与原类相同, 因此你可将其传递给任何一个使用实际服务对象的客户端

目的

提供对象的替代品或其占位符。
代理控制着对于原对象的访问, 并允许在将请求 提交给对象 前 后 进行一些处理。让你能提供真实服务对象的替代品给客户端使用。
代理接收客户端的请求并进行一些处理 (访问控制和缓存等), 然后再将请求传递给服务对象
信用卡是 银行账户的代理, 银行账户 则是 一大捆现金 的代理。
它们都实现了同样的接口, 均可用于进行支付。
消费者会非常满意, 因为不必随身携带大量现金; 商店老板同样会十分高兴, 因为交易收入能以电子化的方式进入商店的银行账户中, 无需担心存款时出现现金丢失或被抢劫的情况。

结构

场景

延迟初始化 (虚拟代理)。 如果你有一个偶尔使用的重量级服务对象一直保持该对象运行会消耗系统资源时, 可使用代理模式

你无需在程序启动时就创建该对象, 可将对象的初始化延迟到真正有需要的时候。

访问控制 (保护代理)。 如果你只希望特定客户端使用服务对象, 这里的对象可以是操作系统中非常重要的部分, 而客户端则是各种已启动的程序 (包括恶意程序), 此时可使用代理模式

代理可 仅在 客户端凭据满足要求时 将请求传递给服务对象。

本地执行远程服务 (远程代理)。 适用于服务对象位于远程服务器上的情形。

代理通过 网络传递客户端请求, 负责处理 所有与网络相关的复杂细节。

记录日志请求 (日志记录代理)。 适用于当你需要保存对于服务对象的请求历史记录时

代理可以在向服务传递请求前进行记录。

缓存请求结果 (缓存代理)。 适用于需要缓存客户请求结果对缓存生命周期进行管理时, 特别是当返回结果的体积非常大时

代理可对重复请求所需的相同结果进行缓存, 还可使用请求参数作为索引缓存的键值。

优缺点

你可以在 客户端 毫无察觉的情况下 控制服务对象。
如果客户端 对服务对象的 生命周期 没有特殊要求, 你可以对生命周期进行管理。
即使服务对象 还未准备好 或 不存在, 代理 也可以正常工作。
开闭原则。 你可以在 不对服务 或 客户端 做出修改的情况下创建新代理。
代码可能会变得复杂, 因为需要新建许多类。
服务响应可能会延迟。

示例代码

@Deprecated

单例模式

java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例、饿汉式单例、登记式单例。

单例模式有以下特点:
1、单例类 只能 有一个实例。
2、单例类必须 自己创建自己 的唯一实例。
3、单例类必须给 所有其他对象 提供这一实例。

单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。

懒汉式单例

在第一次调用的时候实例化自己

public class Singleton {private Singleton() {}private static Singleton single=null;//静态工厂方法 public static Singleton getInstance() {if (single == null) {  single = new Singleton();}  return single;}
}

Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。

没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例,要实现线程安全,有以下三种方式,都是对getInstance这个方法改造,保证了懒汉式单例的线程安全

//在getInstance方法上加同步
public static synchronized Singleton getInstance() {if (single == null) {  single = new Singleton();}  return single;
}
//双重检查锁定
public static Singleton getInstance() {if(singleton == null) {  synchronized (Singleton.class) {  if (singleton == null) {  singleton = new Singleton(); }  }  }  return singleton;
}
//静态内部类
//这种比上面1、2都好一些,既实现了线程安全,又避免了同步带来的性能影响。
public class Singleton {  private static class LazyHolder {  private static final Singleton INSTANCE = new Singleton();  }  private Singleton (){}  public static final Singleton getInstance() {  return LazyHolder.INSTANCE;  }
}
第1种,在方法调用上加了同步,虽然线程安全了,但是每次都要同步,会影响性能,毕竟99%的情况下是不需要同步的
第2种,在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗
第3种,利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗,所以一般我倾向于使用这一种。

饿汉式单例

在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。

//饿汉式单例类.在类初始化时,已经自行实例化
public class Singleton1 {private Singleton1() {}private static final Singleton1 single = new Singleton1();//静态工厂方法 public static Singleton1 getInstance() {return single;}
}

区别

饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了
而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。

1、线程安全:

饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题
懒汉式本身是非线程安全的,为了实现线程安全有几种写法,分别是上面的1、2、3,这三种实现在资源加载和性能方面有些区别。

2、资源加载和性能:

饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,
但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始
化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。

至于1、2、3这三种实现懒汉线程安全的方式又有些区别

工厂模式

简单工厂模式(Simple Factory Pattern),又叫静态工厂方法模式(Static FactoryMethod Pattern),是通过专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。

简单工厂模式中包含的角色及其相应的职责如下

工厂角色(Creator):这是简单工厂模式的核心,由它负责创建所有的类的内部逻辑。当然工厂类必须能够被外界调用,创建所需要的产品对象。抽象(Product)产品角色:简单工厂模式所创建的所有对象的父类,注意,这里的父类可以是接口也可以是抽象类,它负责描述所有实例所共有的公共接口。具体产品(Concrete Product)角色:简单工厂所创建的具体实例对象,这些具体的产品往往都拥有共同的父类。

简单工厂模式解决的问题是如何去实例化一个合适的对象。

简单工厂模式的核心思想就是:有一个专门的类来负责创建实例的过程。

具体来说,把产品看着是一系列的类的集合,这些类是由某个抽象类或者接口派生出来的一个对象树。而工厂类用来产生一个合适的对象来满足客户的要求。

应用

Java中的JDBC操作数据库

Java程序通过JDBC可以执行SQL语句,对获取的数据进行处理,并将变化了的数据存回数据库,因此,JDBC是Java应用程序与各种关系数据进行对话的一种机制。用JDBC进行数据库访问时,要使用数据库厂商提供的驱动程序接口与数据库管理系统进行数据交互。

客户端要使用使用数据时,只需要和工厂进行交互即可,这就导致操作步骤得到极大的简化,操作步骤按照顺序依次为:

注册并加载数据库驱动,一般使用Class.forName();
创建与数据库的链接Connection对象
创建SQL语句对象preparedStatement(sql)
提交SQL语句,根据实际情况使用executeQuery()或者executeUpdate();
显示相应的结果;关闭数据库。

适配器模式

将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作。


模式中的角色

1 目标接口(Target):客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口。
2 需要适配的类(Adaptee):需要适配的类或适配者类。
3 适配器(Adapter):通过包装一个需要适配的对象,把原接口转换成目标接口。  

代理模式

比如在玩“极品飞车”这款游戏,如果游戏者手中的金钱达到了一定的数量就可以到车店买一部性能更高的赛车,那么这个卖车的“车店”就是一个典型的“汽车厂家”的“代理”,他为汽车厂家“提供卖车的服务”给有需求的人士。

对一些对象提供代理,以限制那些对象去访问其它对象。

代理模式主要使用了java的多态,干活的是被代理类,代理类主要是接活,你让我干活,好,我交给幕后的类去干,你满意就成,那怎么知道被代理类能不能干呢?同根就成,大家知根知底,你能做啥,我能做啥都清楚得很,同样一个接口。

java面试常见设计模式相关推荐

  1. java面试常见面试问题_Java面试准备:15个Java面试问题

    java面试常见面试问题 并非所有的访谈都将重点放在算法和数据结构上-通常,访谈通常只侧重于您声称是专家的语言或技术.在此类访谈中,通常没有任何"陷阱"问题,而是它们要求您利用内存 ...

  2. Java面试常见知识点总结

    目录 面试常见知识点 静态代码块 代码块 构造方法之间的顺序 interface和abstract的区别 abstract能不能继承interface 反正可不可以 interface 和abstra ...

  3. 京东深资架构师告诉你Java面试常见知识点(建议收藏)

    后端架构师 专注研究 Java 核心技术.架构,不限于分享算法.架构.高并发.多线程.JVM.Spring Boot.Maven.分布式.Spring Cloud +Docker+k8s.Dubbo. ...

  4. 自动装箱自动拆箱java,自动装箱?拆箱?==问题?详解java面试常见的一个问题...

    1:前言 相信大家都在面试中都被问到过一个问题,这个问题也是近年来面试官刁难人比较常见的一个问题,所以也被大家所熟知了,本质上也很简单,但是也是非常基础的一个题目. Integer a = 100; ...

  5. Java面试常见算法

    在程序员的职业生涯中,算法亦算是一门基础课程,尤其是在面试的时候,很多公司都会让程序员编写一些算法实例,例如快速排序.二叉树查找等等. 本文总结了程序员在代码面试中最常遇到的10大算法类型,想要真正了 ...

  6. Java 面试常见项目问题回答

    之前整理了好几期,我面试时遇到的面试候选人,我是如何我去筛选的,这一期,我们来看下一些 面试常问的业务性的问题 你们公司权限认证是如何实现的? 这其实是个通用性的问题,大部分公司 小型公司,或者中型公 ...

  7. Java面试之设计模式七大原则

    最近项目不太忙,不怎么加班,正利用晚上时间好好学习学习设计模式,之前可能多多少少都用到过,但是有些还是很模糊,这下正好系统的学一下. 好了,话不多说,进入正题. 1.什么是设计模式? 软件工程中,设计 ...

  8. 互联网IT就业市场,你要说最有市场的一定是它,java面试常见笔试题

    ♦ Java就业前景 ♦ Java工程师薪资 ♦ 未来发展 下面,播妞将针对这 3 个问题给大家进行详细解答,希望能够给处在"慌"金时代的你带来一些启发. Java 就业景气指数 ...

  9. 2020 年 Java 面试常见 350 题

    最新面试题汇总,来啦! 关注下方公众号,回复"2020面试" 免费获取2020年最新面试题 一起进大厂,每日学干货 关注我,不迷路

最新文章

  1. 基于YOLOv5模型压缩、模型量化、模型剪枝
  2. ​卷积层和分类层,哪个更重要?
  3. android+button+不可点击置灰,android:tint 给imagebutton着色 按钮灰色
  4. 18. 二叉树的镜像
  5. Juniper SSG 550M HA配置文档
  6. 【codevs1004】四子连棋
  7. linux定时备份Mysql
  8. 《决战大数据大数据的关键思考 升级版》PDF电子书分享
  9. 【例】系统顺序图、操作契约、领域模型图
  10. pmp 第4章 错题整理(不定时更新)
  11. 【计算机网络】SIP会话时,使用重定向与不使用重定向功能的区别
  12. Hudson poll scm 时间格式说明
  13. SQL SERVER 2000 自动下载木马病毒 cmd.exe和ftp.exe解决办法
  14. Error:(list) object cannot be coerced to type 'double'的处理
  15. xml保存图片和读取图片(一)
  16. Android应用商店的软件安全性到底如何?
  17. 项目案例:网上书店数据库设计
  18. 房贷新政冷冻楼市 炒楼热钱流向股市
  19. 线程基础篇-线程同步
  20. react项目中在线预览附件

热门文章

  1. Java jdk源代码的Math包
  2. 异常:egret获取引擎列表失败
  3. 项目管理100问 | NO.6 如何为项目制定里程碑?
  4. 【批量解压d文件】d文件是GNSS观测数据文件
  5. CentOS7(8)安装/卸载MySQL
  6. Phonetic symbol 辅音 - 清辅音 -- /ʃ/
  7. 双模控制器很耗电_电动车双模控制器什么意思
  8. 人教版 初步使用计算机 教案,人教版小学信息三上第5课益智游戏练技能教案与课件配套5篇...
  9. Redis的基本使用
  10. 习题课3-1(动态规划)