概念
模式的基本概念可以看作是程序设计的基本概念-添加抽象层, 当你抽象一些东西的时候就像是在剥离特定的细节,而这背后的重要动机就是将易变的事物跟不易变的事物分开
另一种方法是,一旦你发现程序的某些部分可能会因为某种原因而发生变化,你要保持这些变化不会引起整个代码的其它变化, 如果代码更容易理解,那么维护起来也就更方便
单例模式
也许单例模式是最简单的模式,它是一种提供一个且只有一个对象实例的方法。
示例:
// patterns/SingletonPattern.java
interface Resource {
int getValue();
void setValue(int x);
}
/*
* 由于这不是从Cloneable基类继承而且没有添加可克隆性,
* 因此将其设置为final可防止通过继承添加可克隆性。
* 这也实现了线程安全的延迟初始化:
*/
final class Singleton {
private static final class ResourceImpl implements Resource {
private int i;
private ResourceImpl(int i) {
this.i = i;
}
public synchronized int getValue() {
return i;
}
public synchronized void setValue(int x) {
i = x;
}
}
private static class ResourceHolder {
private static Resource resource = new ResourceImpl(47);
}
public static Resource getResource() {
return ResourceHolder.resource;
}
}
public class SingletonPattern {
public static void main(String[] args) {
Resource r = Singleton.getResource();
System.out.println(r.getValue());
Resource s2 = Singleton.getResource();
s2.setValue(9);
System.out.println(r.getValue());
try {
// 不能这么做,会发生:compile-time error(编译时错误).
// Singleton s3 = (Singleton)s2.clone();
} catch(Exception e) {
throw new RuntimeException(e);
}
}
} /* Output: 47 9 */
在这里,这是通过在Singleton类中将Resource的实现作为私有类来实现的。此时,你将决定如何创建对象。在这里,它是按需创建的,在第一次访问的时候创建。 该对象是私有的,只能通过public getResource()方法访问。
创建单例的关键是防止客户端程序员直接创建对象。
模式分类
创建型:即如何创建对象, 这通常涉及隔离对象的创造细节,这样你的代码就不依赖于具体的对象的类型。单例模式, 工厂模式都是创建型
构造型:设计对象以满足特定的项目约束。它处理对象与对象之间的连接方式,以确保系统中的更改并不会改变这些连接
行为型:处理程序中特定类型的操作的对象。这些封装要执行的过程,例如解释语言、实现请求、遍历序列(如在迭代器中)或实现算法。本章包含观察者和访问者模式的例子。
构建应用程序框架
应用程序框架允许你从一个类或一组类开始,创建一个新应用, 你可以重用现有类的大部分代码,并根据需求来覆盖一个或者多个方法,来定制应用
模板方法模式
应用程序框架的一个基本的概念就是模板方法模式,它因此在程序的底层, 它的一个重要特点就是基本上是它是定义在基类里的,大部分都是final修饰的不可以修改的或者private的, 它会调用一些可被你修改(重写)的方法来完成工作, 但它通常只能作为初始化的一部分而被调用(你不一定能够直接去调用它)。
// patterns/TemplateMethod.java
// Simple demonstration of Template Method
abstract class ApplicationFramework {
ApplicationFramework() {
templateMethod(); //看出只有在初始化的时候会调用templateMethod()
}
abstract void customize1();
abstract void customize2();
// "private" means automatically "final":
private void templateMethod() {
IntStream.range(0, 5).forEach(
n -> {
customize1();
customize2();
});
}
}
// Create a new "application":
class MyApp extends ApplicationFramework {
@Override
void customize1() {
System.out.print("Hello ");
}
@Override
void customize2() {
System.out.println("World!");
}
}
public class TemplateMethod {
public static void main(String[] args) {
new MyApp();
}
}
/* Output:
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
*/
基类构造函数负责执行必要的初始化。框架使用者只提供 customize1() 和 customize2() 的定义,然后“应用程序”已经就绪运行。
面向实现
代理模式跟桥接模式都提供了代码中可使用的代理类
相同点:都是通过操纵代理类来调用相关的实现类进行操作
不同点:代理有长期跟专门的意思, 结构上 代理模式只有一个实现,而桥接模式有多个实现,代理模式是用于控制对实现类的访问, 而桥接模式是允许你可以动态的修改实现
但是如果你拓展了控制对实现的访问这一概念,那么两者就可以完美的结合在一起
代理模式
// patterns/ProxyDemo.java
// Simple demonstration of the Proxy pattern
interface ProxyBase {
void f();
void g();
void h();
}
class Proxy implements ProxyBase {
private ProxyBase implementation;
Proxy() {
implementation = new Implementation();
}
// Pass method calls to the implementation:
@Override
public void f() { implementation.f(); }
@Override
public void g() { implementation.g(); }
@Override
public void h() { implementation.h(); }
}
class Implementation implements ProxyBase {
public void f() {
System.out.println("Implementation.f()");
}
public void g() {
System.out.println("Implementation.g()");
}
public void h() {
System.out.println("Implementation.h()");
}
}
public class ProxyDemo {
public static void main(String[] args) {
Proxy p = new Proxy();
p.f();
p.g();
p.h();
}
}
/*
Output:
Implementation.f()
Implementation.g()
Implementation.h()
*/
从上面可以看出,Proxy代理了Implementation
状态模式/桥接模式
状态模式向代理对象添加了更多的实现,以及在代理对象的生命周期内从一个实现切换到另一种实现的方法
// patterns/StateDemo.java // Simple demonstration of the State pattern
interface StateBase {
void f();
void g();
void h();
void changeImp(StateBase newImp);
}
class State implements StateBase {
private StateBase implementation;
State(StateBase imp) {
implementation = imp;
}
@Override
public void changeImp(StateBase newImp) {
implementation = newImp;
}
// Pass method calls to the implementation:
@Override
public void f() {
implementation.f();
}
@Override
public void g() {
implementation.g();
}
@Override
public void h() {
implementation.h();
}
}
class Implementation1 implements StateBase {
@Override
public void f() {
System.out.println("Implementation1.f()");
}
@Override
public void g() {
System.out.println("Implementation1.g()");
}
@Override
public void h() {
System.out.println("Implementation1.h()");
}
@Override
public void changeImp(StateBase newImp) {
}
}
class Implementation2 implements StateBase {
@Override
public void f() {
System.out.println("Implementation2.f()");
}
@Override
public void g() {
System.out.println("Implementation2.g()");
}
@Override
public void h() {
System.out.println("Implementation2.h()");
}
@Override
public void changeImp(StateBase newImp) {
}
}
public class StateDemo {
static void test(StateBase b) {
b.f();
b.g();
b.h();
}
public static void main(String[] args) {
StateBase b =
new State(new Implementation1());
test(b);
b.changeImp(new Implementation2());
test(b);
}
}
/* Output:
Implementation1.f()
Implementation1.g()
Implementation1.h()
Implementation2.f()
Implementation2.g()
Implementation2.h()
*/
在main()中,首先使用第一个实现,然后改变成第二个实现。代理模式和状态模式的区别在于它们解决的问题。
设计模式中描述的代理模式的常见用途如下:
- 远程代理。它在不同的地址空间中代理对象。远程方法调用(RMI)编译器rmic会自动为您创建一个远程代理。
- 虚拟代理。这提供了“懒加载”来根据需要创建“昂贵”的对象。
- 保护代理。当您希望对代理对象有权限访问控制时使用。
- 智能引用。要在被代理的对象被访问时添加其他操作。例如,跟踪特定对象的引用数量,来实现写时复制用法,和防止对象别名。一个更简单的例子是跟踪特定方法的调用数量。您可以将Java引用视为一种保护代理,因为它控制在堆上实例对象的访问(例如,确保不使用空引用)。
状态机
桥接模式允许程序员改变实现,而状态机就是利用一个结构来自动的实现代理的实现切换
当前的实现表示当前系统的状态,不同状态下的行为不同(因为实现不同), 基本上这是一个利用对象的”状态机”, 将系统从一种状态移至另一种状态的代码通常是模板方法模式
如下示例所示 ```java // patterns/state/StateMachineDemo.java // The StateMachine pattern and Template method // {java patterns.state.StateMachineDemo} package patterns.state;
import onjava.Nap;
interface State { void run(); }
abstract class StateMachine { protected State currentState;
protected abstract boolean changeState();
// Template method:
protected final void runAll() {
while (changeState()) // Customizable
currentState.run();
}
}
// A different subclass for each state:
class Wash implements State { @Override public void run() { System.out.println(“Washing”); new Nap(0.5); } }
class Spin implements State { @Override public void run() { System.out.println(“Spinning”); new Nap(0.5); } }
class Rinse implements State { @Override public void run() { System.out.println(“Rinsing”); new Nap(0.5); } }
class Washer extends StateMachine { private int i = 0; // The state table: private State[] states = { new Wash(), new Spin(), new Rinse(), new Spin(), };
Washer() {
runAll();
}
@Override
public boolean changeState() {
if (i < states.length) {
// Change the state by setting the
// surrogate reference to a new object:
currentState = states[i++];
return true;
} else
return false;
}
}
public class StateMachineDemo { public static void main(String[] args) { new Washer(); } } / Output: Washing Spinning Rinsing Spinning /
<a name="b98e9d7c"></a>
## 工厂模式
强制对象的创建都通过通用工厂进行,而不是允许创建代码在整个系统中传播。 如果你程序中的所有代码都必须执行通过该工厂创建你的一个对象,那么在添加新类时只需要修改工厂即可。<br />由于每个面向对象的程序都会创建对象,并且很可能会通过添加新类型来扩展程序,因此工厂是最通用的设计模式之一。<br />举例来说,让我们重新看一下**Shape**系统。 首先,我们需要一个用于所有示例的基本框架。 如果无法创建**Shape**对象,则需要抛出一个合适的异常:
```java
// patterns/shapes/BadShapeCreation.java package patterns.shapes;
public class BadShapeCreation extends RuntimeException {
public BadShapeCreation(String msg) {
super(msg);
}
}
接下来,是一个Shape基类:
// patterns/shapes/Shape.java
package patterns.shapes;
public class Shape {
private static int counter = 0;
private int id = counter++;
@Override
public String toString(){
return getClass().getSimpleName() + "[" + id + "]";
}
public void draw() {
System.out.println(this + " draw");
}
public void erase() {
System.out.println(this + " erase");
}
}
该类自动为每一个Shape对象创建一个唯一的id
。toString()
使用运行期信息来发现特定的Shape子类的名字。
现在我们能很快创建一些Shape子类了:
// patterns/shapes/Circle.java
package patterns.shapes;
public class Circle extends Shape {}
// patterns/shapes/Square.java
package patterns.shapes;
public class Square extends Shape {}
// patterns/shapes/Triangle.java
package patterns.shapes;
public class Triangle extends Shape {}
工厂是具有能够创建对象的方法的类。 我们有几个示例版本,因此我们将定义一个接口:
// patterns/shapes/FactoryMethod.java
package patterns.shapes;
public interface FactoryMethod {
Shape create(String type);
}
create()
接收一个参数,这个参数使其决定要创建哪一种Shape对象,这里是String
,但是它其实可以是任何数据集合。对象的初始化数据(这里是字符串)可能来自系统外部。 这个例子将测试工厂:
// patterns/shapes/FactoryTest.java
package patterns.shapes;
import java.util.stream.*;
public class FactoryTest {
public static void test(FactoryMethod factory) {
Stream.of("Circle", "Square", "Triangle",
"Square", "Circle", "Circle", "Triangle")
.map(factory::create)
.peek(Shape::draw)
.peek(Shape::erase)
.count(); // Terminal operation
}
}
创建工厂的一种方法是显式创建每种类型:
// patterns/ShapeFactory1.java
// A simple static factory method
import java.util.*;
import java.util.stream.*;
import patterns.shapes.*;
public class ShapeFactory1 implements FactoryMethod {
public Shape create(String type) {
switch(type) {
case "Circle": return new Circle();
case "Square": return new Square();
case "Triangle": return new Triangle();
default: throw new BadShapeCreation(type);
}
}
public static void main(String[] args) {
FactoryTest.test(new ShapeFactory1());
}
}
输出结果为
Circle[0] draw
Circle[0] erase
Square[1] draw
Square[1] erase
Triangle[2] draw
Triangle[2] erase
Square[3] draw
Square[3] erase
Circle[4] draw
Circle[4] erase
Circle[5] draw
Circle[5] erase
Triangle[6] draw
Triangle[6] erase
类实现了Supplier <类型>,因此可用于通过Stream.generate()
创建Stream
函数对象
一个 函数对象 封装了一个函数。其特点就是将被调用函数的选择与那个函数被调用的位置进行解耦
命令模式
从最直观的角度来看,命令模式就是一个函数对象:一个作为函数的对象, 我们可以将 函数对象 作为参数传递给其他的方法或者对象, 来执行特定的操作
在Java 8之前,想要产生单个函数的效果,我们必须明确将方法包含在对象中,而这需要太多的仪式了。而利用Java 8的lambda特性, 命令模式 的实现将是微不足道的。
// patterns/CommandPattern.java
import java.util.*;
public class CommandPattern {
public static void main(String[] args) {
List<Runnable> macro = Arrays.asList(
() -> System.out.print("Hello "),
() -> System.out.print("World! "),
() -> System.out.print("I'm the command pattern!")
);
macro.forEach(Runnable::run);
}
}
/* Output:
Hello World! I'm the command pattern!
*/
这里macro就是对象. macro.forEach(Runnable::run)就是实现了命令模式, 将Runnable::run的动作传递给forEach
策略模式
策略模式 看起来像是从同一个基类继承而来的一系列 命令 类。但是仔细查看 命令模式,你就会发现它也具有同样的结构:一系列分层次的 函数对象。不同之处在于,这些函数对象的用法和策略模式不同。就像前面的 io/DirList.java
那个例子,使用 命令 是为了解决特定问题 — 从一个列表中选择文件。“不变的部分”是被调用的那个方法,而变化的部分被分离出来放到 函数对象 中。我认为 命令模式 在编码阶段提供了灵活性,而 策略模式 的灵活性在运行时才会体现出来。尽管如此,这种区别却是非常模糊的。
责任链模式
责任链模式 也许可以被看作一个使用了 策略 对象的“递归的动态一般化”。此时我们进行一次调用,在一个链序列中的每个策略都试图满足这个调用。这个过程直到有一个策略成功满足该调用或者到达链序列的末尾才结束。在递归方法中,一个方法将反复调用它自身直至达到某个终止条件;使用责任链,一个方法会调用相同的基类方法(拥有不同的实现),这个基类方法将会调用基类方法的其他实现,如此反复直至达到某个终止条件。
改变接口
有时候我们需要解决的问题很简单,仅仅是“我没有需要的接口”而已。有两种设计模式用来解决这个问题:适配器模式 接受一种类型并且提供一个对其他类型的接口。外观模式 为一组类创建了一个接口,这样做只是为了提供一种更方便的方法来处理库或资源。
适配器模式(Adapter)
当我们手头有某个类,而我们需要的却是另外一个类,我们就可以通过 适配器模式 来解决问题。唯一需要做的就是产生出我们需要的那个类,有许多种方法可以完成这种适配。
外观模式(Façade)
当我想方设法试图将需求初步(first-cut)转化成对象的时候,通常我使用的原则是:
“把所有丑陋的东西都隐藏到对象里去”。
基本上说,外观模式 干的就是这个事情。如果我们有一堆让人头晕的类以及交互(Interactions),而它们又不是客户端程序员必须了解的,那我们就可以为客户端程序员创建一个接口只提供那些必要的功能。
外观模式经常被实现为一个符合单例模式(Singleton)的抽象工厂(abstract factory)。当然,你可以通过创建包含 静态 工厂方法(static factory methods)的类来达到上述效果。
// patterns/Facade.java
class A { A(int x) {} }
class B { B(long x) {} }
class C { C(double x) {} }
// Other classes that aren't exposed by the
// facade go here ...
public class Facade {
static A makeA(int x) { return new A(x); }
static B makeB(long x) { return new B(x); }
static C makeC(double x) { return new C(x); }
public static void main(String[] args) {
// The client programmer gets the objects
// by calling the static methods:
A a = Facade.makeA(1);
B b = Facade.makeB(1);
C c = Facade.makeC(1.0);
}
}