设计模式

This is my blog.

软件工程这门课当时只是粗浅认识,便匆匆应付考试了

对于软件工程包括软件体系结构这几门课程,确实学习的不理想

看到书架上的《设计模式》,决定再来学习一番

前言

  • 良好的OO设计必须具备可复用、可扩充、可维护三个特性
  • 模式被认为是历经验证的OO设计经验

模式的概念

模式是在某情境(context)下,针对某问题的某种解决方案

情境就是应用某个模式的情况,是会不断出现的情况

问题就是你想在某情境下达到的目标,但也可以是某情境下的约束

解决方案就是你所追求的:一个通用的设计,用来解决约束,达到目标

模式的分类

根据模式的目标,可以分成三个不同类目:创建型、行为型和结构型

  • 创建型模式设计到将对象实例化,这类模式都提供一个方法,将客户从所需要实例化的对象解耦
    • Singleton
    • Abstract Factory
    • Factory Method
    • Builder
    • Prototype
  • 行为型模式都涉及到对象如何交互分配职责
    • Template Method
    • Command
    • Strategy
    • State
    • Observer
    • Iterator
    • Mediator
    • Visitor
    • Interpreter
    • Chain of Responsibility
    • Memento
  • 结构型模式可以把类或对象组合到更大的结构中、
    • Decorator
    • Composite
    • Proxy
    • Facade
    • Adapter
    • Flyweight
    • Bridge

模式还可以根据所处理的还是对象进行分类

    • Template Method
    • Factory Method
    • Adapter
    • Interpreter
  • 对象

一些建议

  1. 保持简单 keep it simple(kiss),优先考虑简单的方案(“杀鸡焉用牛刀”)
  2. 设计模式非万灵丹
  3. 是否需要模式,用什么模式以及用了模式之后产生的后果
  4. 当未来可能发生的实际改变时,考虑模式
  5. 并非只有在设计时才考虑引进模式,在重构时也要这样做(看是否可以让它拥有更好的结构)
  6. 当系统变得过于复杂,并且不需要预留弹性的时候,需要删除模式

反模式

反模式告诉你如何采用一个不好的解决方案解决一个问题

通过将不好的解决方案归档,帮助其他开发人员避免犯同样的错误;会告诉你为何这个方法从长远看会造成不好的印象,同时指出正确的方向

策略模式 Strategy

书中例子:不同鸭子不同行为

定义

策略模式定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户


封装可以互换的行为,并使用委托决定要使用哪个


情景:

新的需求增加,使某方面的代码发生变化


假方案:

继承:所有子类都会继承,对于不想继承的子类,需要通过覆盖去改变,但是需要对每一个超类的方法对于每一个子类进行检查,并且对于有不同行为的需要覆盖

接口:但是当有大部分类需要此接口的时候,都需要实现接口,无法实现代码的复用

设计原则

  1. 把会变化的部分取出来并封装起来,以便以后可以轻易地改动或扩充此部分,而不影响不需要变化的其他部分

  2. 针对接口/超类型supertype编程,而不是实现编程
    对于实现编程来说,我们通常会Dog d = new Dog(); d.bark();;而对于接口/超类型编程来说,我们不必知道子类具体如何动作的,而是在运行时才指定具体实现的对象,因此我们会Animal animal = new Dog(); animal.makeSound();

  3. 多用组合,少用继承,使用组合有很大的弹性

    继承是在编译时静态决定的,而且所有的子类都会继承到相同的行为;
    而组合的做法能够扩展对象的行为,就可以在运行时动态地进行扩展


解决方案:

​ 通过设计原则实现了接口和实现接口的类之后,在原来的超类中增加实例变量FlyBehavior flyBehavior;,即声明为接口类型(而不是具体类实现类型FlywithWingsorFlyNoWay),并增加对应的方法performFly()。此方法中调用接口方法flyBehavior.fly();。接下来便是具体到子类中了:

1
2
3
4
5
public class MallardDuck extends Duck {
public MallardDuck(){
flyBehavior = new FlywithWings();//这样在调用performFly()的时候,就会实现会飞了,若是创建的是new FlyNoWay();则调用performFly()的时候,就不会飞了
}
}

​ 更进一步的,我们可以更好地利用继承中的动态特性

​ 比如我们可以在运行时想改变鸭子的行为,这时候我们只需要:

1
2
3
4
5
6
7
8
9
10
11
12
13
//在超类中增加一个方法
public void setFlyBehavior(FlyBehavior fb) {
flyBehavior = fb;
}
//若刚开始的模型鸭是不会飞的,但是技术更新后,模型鸭可以飞行了,那么在我们的测试类中可以如下:
public class DuckSimulator {
public static void main(String[] args) {
Duck model = new ModelDuck();
model.performFly();//I can't fly
model.setFlyBehavior(new FlywithWings());
model.performFly();//I'm flying!
}
}

观察者模式 Observer

书中例子:气象布告牌与气象站的推拉

定义

观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新

存在两种:

  • 主题,在变化的时候, PUSH给所有的观察者
    • 但这样会造成每个观察者接收到相同的数据,因此对于某个观察者来说,存在一些数据是不需要的
  • 观察者在需要的时候从主题中 PULL
    • 存在何时去拉的问题,需要不断询问,造成资源的浪费

让对象能够在状态改变的时候被通知


松耦合:

主题subject和观察者observer之间是松耦合的,主题不需要知道具体观察者,只需要知道观察者列表,在有变化的时候,通知他们即可,同时观察者的增加、删除、改变对主题无影响

设计原则

  1. 为了交互对象之间的松耦合而努力

Java内置实现

java.util包中的Observer接口和Observable

  • Observable类【主题】
    • addObserver()
    • deleteObserver()
    • notifyObserver()
    • setChanged()
  • Observer接口【观察者】
    • update()

swing API:JButton

ActionListener倾听可能发生在按钮上的动作,例如按下按钮

装饰者模式 Decorator

书中例子:咖啡店

记忆提示:好多个圈圈,圈圈里套圈圈,表示装饰外有装饰外有装饰……

定义

装饰者模式动态地将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案

首先设计一个Decorator继承超类,表示所有装饰者的接口(也可以是抽象类),然后对于每一个装饰者中一定含有一个实例变量Component wrappedObj,表示被装饰者,同时需要继承超类的方法


包装一个对象,以提供新的行为


缺点:常常造成设计中有大量的小类,数量太多,造成使用此API程序员的困扰

设计原则

  1. 类应该对扩展开放,对修改关闭
    对于已经解决bug的代码不应该修改,对类需要扩展需求则需要满足
    但是并不需要对每一处都采用开放-关闭原则,这会造成浪费,也没必要,还会导致代码变得复杂且难以理解

Java内置实现

圈圈图:从内到外包含的圆分别是:FileInoutStream、BufferedInputStream、LIneNumberInputStream

FileInputStream被装饰的“组件”,提供基本的字节读取功能

BufferedInputStream是一个具体的“装饰者”,加入两种行为:利用缓冲输入来改善性能;用一个readline()方法(用来一次读取一行文本输入数据)来增强接口

LineNumberInputStream也是一个具体的“装饰者”,加上了计算行数的能力

工厂模式 Factory

书中例子:披萨连锁店

定义

工厂方法模式 Factory Method

工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类

工厂方法模式通过子类决定该创建的对象是什么,来达到将对象创建的过程封装的目的


子类决定要创建的具体类是哪一个


声明一个工厂的方法:

abstract Product factoryMethod(string type)

例如:protected abstract Pizza creatPizza(String type)


产品类和创建者类是平行的类层级,它们都有抽象类,而抽象类都有许多具体的子类,每个子类都有自己特定的实现

抽象工厂模式 Abstract Factory

抽象工厂模式提供了一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类


允许客户创建对象家族,而无需指定他们的具体类


抽象工厂中的方法是工厂方法


抽象工厂&工厂方法

  • 都是负责创建对象
  • 工厂方法通过继承;而抽象工厂通过组合
  • 利用工厂方法创建对象,需要扩展一个类,并覆盖它的工厂方法;工厂方法是用来创建对象的,由子类来负责决定具体类型
  • 抽象工厂提供一个用来创建一个产品家族的抽象类型,这个类型的子类定义了产品被产生的方法;但是当扩展的时候,必须改变接口

设计原则

  1. 依赖倒置原则:要依赖抽象,不要依赖具体类

    不能让高层组件依赖低层组件,而且不管高层或低层组件,“两者“都应该依赖于抽象

    所谓”高层”组件,是由其他低层组件定义其行为的类

    比如在这里:披萨店这个高层组件依赖于具体的比萨类了,这是不可以的

    指导方针:

    • 变量不可以持有具体类的引用
    • 不要让类派生自具体类
    • 不要覆盖基类中已实现的方法

    注:尽量达到这个原则,而不用随时都要遵循这个原则;比如,有一个不会改变的类,则直接实例化具体类也就无碍;实例化字符串对象


    在未应用此原则之前:

应用此原则后:(明显可以看到箭头的方向,也变明白了“倒置”)

工厂方法模式2

单件模式 Singleton

书中例子:巧克力工厂

定义

单件模式确保一个类只有一个实例,并提供一个全局访问点


确保有且只有一个对象被创建


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Singleton{
private static Singleton uniqueInstance;
private Singleton(){}
public static synchronized Singleton getInstance(){
// static静态方法,可以在任何地方访问,并且单件可以延迟实例化
// synchronized同步,可以解决在多线程的时候,出现多个实例的情况
// 但是同步降低速度,而且只有在刚开始设置的时候,需要同步;因此在getInstance()对性能不是很关键的时候,就还是保留同步;对于急切创建实例(或者一定会创建的),不用延迟实例化的做法
// 或者通过“双重检查加锁”的方法
if(uniqueInstance == null){
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}

不延迟实例化的做法:

1
2
3
4
5
6
7
8
public class Singleton{
private static Singleton uniqueInstance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return uniqueInstance;
}
}

双重检查加锁的方法:

(java 1.4及其更早版本不可以用此方法,因为对volatile不同)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton{
//volatile 关键词确保:当uniqueInstance被初始化成Singleton实例时,多个线程正确地处理uniqueInstance变量
private volatile static Singleton uniqueInstance;
private Singleton(){}
public static Singleton getInstance(){
if(uniqueInstance == null){
synchronized (Singleton.class){
uniqueInstance = new Singleton();
}
}
return uniqueInstance;
}
}

注:

  • 对于每个类加载器都定义了一个命名空间的时候,仍然会使单件模式失效,可以自行指定类加载器,并指定同一个类加载器
  • 对于类的单件(将每个方法变量均设置为static)的方法,控制权在java上,易混乱
  • java 1.2前,垃圾收集器会清理掉单件,造成单件在没有全局的引用时被当作垃圾清除,除非先建立注册表
  • 单件类不适合继承,继承前必须使基类实现注册功能
  • 单件模式相比全局变量有延迟实例化的优点,并且全局变量不能保证只有一个实例

命令模式 Command

书中例子:遥控器

定义

命令模式“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作


封装请求称为对象


1
2
3
4
public interface Command {
public void execute();
public void undo();
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class LightOnCommand implement Command {
Light light;
public LightOnCommand(Light light) {
this.light = light;
}
public void execute() {
light.on();
}
public void undo(){
light.off();
}
}
public class LightOffCommand implement Command {
Light light;
public LightOffCommand(Light light) {
this.light = light;
}
public void execute() {
light.off();
}
public void undo(){
light.on();
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class RemoteControl {
Command[] onCommands;
Command[] offCommands;
Command undoCommand; //若要实现可以一直撤销的,则用栈的类型
public RemoteControl() {
onCommands = new Command[7];
offCommand = new Command[7];
Command noCommand = new NoCommand();
for (int i = 0; i < 7; i++) {
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
undoCommand = noCommand;
}
public void setCommand(int slot, Command onCommand, Command offCommand){
onCommands[slot] = onCommand;
offCommands[slot] = offCommand;
}
public void onButtonWasPressed(int slot) {
onCommands[slot].execute();
undoCommands = onCommands[slot];
}
public void offButtonWasPressed(int slot) {
offCommands[slot].execute();
undoCommands = offCommands[slot];
}
public void undoButtonWasPushed() {
undoCommand.undo();
}
public String toString() { //覆盖
StringBuffer stringBuff = new StringBuffer();
stringBuff.append("\n------ Remote Control -------\n");
for (int i = 0; i < onCommands.length; i++) {
stringBuff.append("[slot " + i + "] " + onCommands[i].getClass().getName()
+ " " + offCommands[i].getClass().getName() + "\n");
}
return stringBuff.tostring();
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class RemoteControlTest {
public static void main(String[] args) {
RemoteControl remote = new SimpleRemoteCOntrol();
Light light = new Light("Living Room");
LightOnCommand lightOn = new LightOnCommand(light);
LightOffCommand lightOff = new LightOffCommand(light);
remote.setCommand(0, lightOn, lightOff);
System.out.println(remote);
remote.onButtonWasPressed(0);
remote.offButtonWasPressed(0);
remote.undoButtonWasPushed();
System.out.println(remote);
remote.offButtonWasPressed(0);
remote.onButtonWasPressed(0);
remote.undoButtonWasPushed();
}
}

对于状态撤销,可以在被调用者加个变量,表面之前的状态,在undo的方法中,根据变量的值,执行对应的状态方法

使用宏命令,设置一个类继承command,设置变量Command commands;,对于其中的方法,就是循环执行一遍

更多应用:队列请求(日程安排,线程池,工作队列)、日志请求(事务)

空对象

空对象:null object

当你不想返回一个有意义的对象时,可以使用空对象

适配器模式 Adapter

书中例子:交流电适配器

定义

适配器模式将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间

分为对象适配器类适配器


封装对象,并提供不同的接口


对象适配器:(使用了对象组合,以修改的接口包装被适配者;被适配者的任何子类,都可以搭配着适配器使用)

1
2
3
4
5
6
7
8
public class Adapter implement Target {
Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
//....覆盖Target的方法
}

类适配器:(在多重继承语言中才可以实现,java中不可实现)

通过Adapter继承被适配者和目标类


注:适配器可以适配多个类(有点像接线板),将接口转变成不同接口,意图是改变接口符合客户的期望

外观模式 Facade

书中例子:家庭影院要打开多个设备,并设置


注:外观模式可以只针对一个拥有复杂接口的类,也可以针对多个类,意图是,提供子系统的一个简化接口;同时,也将客户从组件的子系统中解耦;同时外观模式也支持直接访问低层接口

定义

外观模式提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用


简化一群类的接口


设计原则

  1. “最少知识原则”:只和你的密友谈话【也称为墨忒尔法则】

    减少对象之间的交互,只留下几个”密友“

    太多的类交互,耦合在一起;当修改系统时,会牵一发而动全身,需要花费更多成本维护,也会因为太复杂而不容易被其他人了解

    指导方针:

    就任何对象而言,在该对象的方法内,我们只应该调用属于以下范围的方法:

    • 该对象本身
    • 被当做方法的参数而传递进来的对象
    • 此方法所创建或实例化的任何对象
    • 对象的任何组件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class car{
    Engine engine; //对象的组件
    publiic car() {
    //初始化发动机
    }
    public void start(Key key){ // 被当做方法的参数而传递进来的对象
    Doors doors = new Doors(); //此方法所创建的对象
    boolean authorized = key.turns();
    if(authorized) {
    engine.start(); //对象组件的方法
    updateDashboardDisplay(); //同一对象内的本地方法
    doors.lock(); //调用你所创建或实例化的对象的方法
    }
    }
    public void updateDashboardDisplay() {
    //更新显示
    }
    }

    注:但是也会导致更多的“包装类”被制造出来,以处理和其他组件的沟通,这可能会导致复杂度和开放时间的增加,并降低运行时的性能

模版方法模式 Template Method

书中例子:泡茶与泡咖啡

定义

模版方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模版方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤


子类决定如何实现一个算法中的步骤


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public abstract class AbstractClass {
final void templateMethod() { //模版方法被声明为final,以免子类改变这个算法的顺序
commonMethod1();
primitiveOperation1();
primitiveOperation2();
hook();
//.....
}
abstract void primitiveOperation1();
abstract void primitiveOperation2();
void commonMethod1() {
// 实现
}
void hook() {}
}
public class ConcreteClass extends AbstractClass{
public void primitiveOperation1() {
//实现
}
public void primitiveOperation2() {
//实现
}
/*
void hook() {
//实现
}
*/
}

一个变种:(不使用继承)

java中的sort方法,只需要比较的元素实现compareTo()方法,而不是设计一个类继承数组


策略模式和模版模式都封装算法,一个用组合,一个用继承

工厂方法是模版方法的一个特殊版本

钩子 hook

默认不做事的方法。子类可以视情况决定要不要覆盖它们

例子:

  • Swing中的JFrame,void paint(Graphics graphics)钩子方法;
  • applet中的,void init()void start()void stop()void destory()void paint(Graphics graphics)等钩子方法

当你子类必须提供算法中某个方法或步骤的实现时,就使用抽象方法;如果算法的这部分是可选的,就用钩子

设计原则

  1. “好莱坞原则”:高层组件调用低层组件,而不允许低层组件调用高层组件

迭代器模式 Iterator

书中例子:煎饼菜单和餐厅菜单的合并

定义

迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示


在对象的集合之中游走,而不暴露集合的实现


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/*
可以直接继承java.util.Iterator,而不用自己创建新的接口,java中的接口还有一个方法叫remove()
remove()在多线程中可能会出现问题
对于ArrayList它自己已经又了一个返回迭代器iterator()的方法了
对于HashTable来说,可以调用values.iterator()方法
*/
public interface Iterator {
boolean hasNext();
Object next();
}
// import java.util.Iterator;
public class ConcreteIterator1 implement Iterator {
ConcreteItem[] items;
public ConcreteIterator1(ConcreteItem[] items) {
this.items = items;
}
public Object next() {
// 实现
}
public boolean hasNext() {
// 实现
}
}
public interface Aggregate {
Iterator createIterator();
}
public class ConcreteAggregate1 implement Aggregate {
ConcreteItem[] items;
public ConcreteAggregate1() {
// 构造器
}
public Iterator createIterator() {
return new ConcreteIterator1(items);
}
//...
}

设计原则

  1. 一个类应该只有一个引起变化的原因

内聚 Cohesion

内聚用来衡量一个类或模块紧密地达到单一目的或责任

当一个模块或一个类被设计成只支持一组相关的功能时,我们说它具有高内聚;反之,当被设计成支持一组不相关的功能时,我们说它具有低内聚

遵守上述设计原则的类很容易具有很高内聚的特点,并且易维护

空迭代器

这个迭代器的hasNext()永远返回false

这样在就不用特判NUll的情况了

Java内置实现

在java 5中,所有的集合都已经新增了对遍历的支持

1
2
3
for (Object obj: collection) {
// ...
}

组合模式 Composite

书中例子:甜品菜单作为菜单的子菜单

定义

组合模式允许你将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合


客户用一致的方式处理对象集合单个对象


注:对于个别对象和对象组合中存在一些差异的方法,如果一个不支持的话,建议抛出UnsupportOperationException异常,所以在抽象类中的方法均定义为异常的,若是之后可以实现,则覆盖

这可能会违反迭代器模式中的设计原则,也会出现在叶节点中增加一个组合对象表示的孩子,出现错误,同时让一个元素是组合还是叶节点对于客户来说都是透明的;所以需要折衷考虑

状态模式 State

书中例子:糖果机游戏

定义

状态模式允许对象在内部状态改变时改变它的行为对象看起来好像修改了它的类


封装了基于状态的行为,并使用委托在行为之间切换


将状态封装成为独立的类,并将动作委托到代表当前状态的对象

对象中有状态这个变量,状态作为一个接口/抽象类,具体状态实现接口,对于内部状态改变时,调用状态的方法,就会多态根据具体的状态作出对应的行为(哇,感觉多态真妙呢!)

状态类之间产生依赖

状态共享需要将每个状态指定到静态实例中


类图和策略模式相同,但是意图不同

一般来说策略模式想成是除了继承之外的一种弹性替代方案,通过组合不同的对象来改变行为

而把状态模式想成是不用在context中放置许多条件判断的替代方案。通过将行为包装进状态对象中,通过在context内简单地改变状态对象改变context行为

代理模式 Proxy

书中例子:CEO需要多地的糖果机报告、加载图片中出现的提示语、用户不可以更改评分和给自己评分的相亲网站

定义

代理模式为另一个对象提供一个替身或占位符以控制对这个对象的访问


包装对象,以控制对此对象的访问


  • 远程代理控制访问远程对象
  • 虚拟代理控制访问创建开销大的资源
    • 例如,加载时,虚拟代理显示“正在加载中”,然后图片加载完,即资源创建完,再显示图片
  • 保护代理基于权限控制对资源的访问
  • 其他
    • 防火墙代理
      • 控制网络资源的访问,保护主题免于“坏客户”的侵害
    • 智能引用代理
      • 当主题被引用时,进行额外的动作,例如计算一个对象被引用的次数
    • 缓存代理
      • 为开销大的运算结果提供暂时存储,它也允许多个客户共享结果,以减少计算或网络延迟
    • 同步代理
      • 在多线程的情况下为主题提供安全的访问
    • 复杂隐藏代理
      • 用来隐藏一个类的复杂集合的复杂度,并进行访问控制。有时候也被称为外观代理(与外观代理不同,代理控制访问,而外观模式只提供一组接口)
    • 写入时复制代理
      • 用来控制对象的复制,方法是延迟对象的复制,直到客户真的需要为止(这是虚拟代理的变体)

装饰者模式是为对象增加行为;代理模式是控制对象的访问

适配器模式会改变对象适配器的接口;代理模式则实现相同的接口

代理模式1

JAVA内置实现

java在java.lang.reflect包中有自己的代理支持,利用这个包你可以在运行时动态创建一个代理类,实现一个或多个接口,并将方法的调用转发到你所指定的类——动态代理

远程代理

RMI提供了客户辅助对象服务辅助对象,不必书写任何网络或I/O代码(存在一定风险和失败的可能性)

客户辅助对象称为桩stub;服务辅助对象称为骨架skeleton

远程服务制作步骤

  1. 制作远程接口(供客户调用的远程方法,是一个接口,是服务的类类型)

    接口:java.rmi.Remote

    1
    2
    3
    4
    5
    6
    7
    import java.rmi.*;
    public interface MyRemote extends Remote {
    //任何方法都需要抛出异常;网络传送会失败
    //任何返回值必须是原语primitive类型或者可序列化Serializable类型;网络需要打包传送
    public String sayHello() throws RemoteException ;
    }
  2. 制作远程实现(客户真正想要调用方法的对象)【糖果机】

    实现远程接口(即客户将要调用的方法的接口)

    扩展java.rmi.server.UnicastRemoteObject,实现远程功能

    不带变量的构造器,并抛出RemoteException异常

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    import java.rmi.*;
    import java.rmi.server.*;
    public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote {
    public String sayHello() { //注意不需要声明RemoteException
    return "Server says, 'Hey'";
    }
    public MyRemoteImpl() throws RemoteException {}
    public static void main (String[] args) {
    try{
    MyRemote service = new MyRemoteImpl();
    Naming.rebing("RemoteHello", service);
    }catch(Exception ex) {
    ex.printStackTrace();
    }
    }
    }
  3. 利用rmic产生的stubskeleton(JDK中有rmic)

    命令行% rmic MyRemoteImpl(不要加.class,直接类名即可)

  4. 启动RMI registry(rmiregistry)【电话簿,客户可以查到代理的位置,即stub helper对象】

    命令行% rmiregistry(在classes目录启动,必须先于开始远程服务

  5. 开始远程服务(实例化一个服务实例,并注册到RMI registry

    注册服务使用了java.rmi.Naming类的静态rebind(String name, Remote service)方法

客户代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.rmi.*;
public class MyRemoteClient {
public static void main (String[] args) {
new MyRemoteClient.go();
}
public void go() {
try{
//Naming.lookup()返回值为Object类型,需要强制转换
//需要指定IP地址或主机名以及服务被绑定/重绑定时用的名称
MyRemote service = (MyRemote) Naming.lookup( "rmi://127.0.0.1/RemoteHello");
String s = service.sayHello();
system.println(s);
}catch(Exception ex) {
ex.printStackTrace();
}
}
}

保护代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import java.lang.reflect.*; //InvocationHandler是java.lang.reflect包的一部分
public class OwnerInvocationHandler implements invocationHandler {
PersonBean Person;
public OwnerInvocationHandler(PersonBean person) {
this.person = person;
}
public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException {
try {
if(method.getName().startsWith("get")) {
return method.invoke(person, args);
} else if(method.getName.equals("setHotOrNotRating")) { //被保护部分
throw new IllegalAccessException();
} else if(method.getName().startsWith("set")){
return method.invoke(person, args);
}
}catch(IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
}
PersonBean getOwnerProxy(PersonBean person) {
return (PersonBean) Proxy.newProxyInstance(
person.getClass.getClassLoader(),
person.getClass.getInterfaces(),
new OwnerInvocationHandler(person));
}

复合模式 Compound

书中例子:DJView,心脏跳动


注:不是某些模式结合在一起使用就是复合模式了,必须具有一般性,适合解决许多问题才行

MVC (Model-View-Controller)

Headfirst里的超可爱的胖胖的大叔唱的呢!


MVC:模型-视图-控制器

模型: 模型持有所有的数据状态程序逻辑。模型没有注意到视图和控制器,虽然它提供了操纵和检索状态的接口,并发送状态改变通知给观察者

视图:用来呈现模型。视图通常直接从模型中取得它需要显示的状态与数据

控制器:取得用户的输入并解读其对模型的意思


MVC是复合模式,结合了观察者模式、策略模式和组合模式

模型利用“观察者”让控制器和视图可以随最新的状态改变而更新

视图和控制器则实现了“策略模式“。控制器是视图的行为/策略,如果你希望有不同的行为,可以直接换一个控制器。

视图内部使用组合模式来管理窗口、按钮以及其他显示组件。每个显示组件不是组合节点(例如窗口),就是叶节点(例如按钮)。当控制器告诉视图更新时,只需告诉视图最顶层的组件即可,组合会处理其余的事。


使用适配器模式将模型适配成符合现有视图和控制器需要的模型


Web

在web中,适配MVC而产生符合浏览器/服务器模型(Model 2),并使用Servlet和JSP技术的结合,来达到MVC的分离效果

servlet——控制器 html/jsp——视图

但控制器不是直接和视图进行交互的,而是通过Bean(BeatModel)

桥接模式 Bridge

书中例子:每部不同型号的电视都有自己的遥控器实现

两难:电视机和遥控器都会发生改变


桥接模式通过将实现抽象放在两个不同的类层次中而使它们可以独立改变

例如,不是将TV直接继承远程控制这个接口,来各自实现,而是将远程控制接口和电视机接口分开,中间以Has-A连接


优点:

  • 将实现予以解耦,让它和界面之间不再永久绑定
  • 抽象和实现可以独立扩展,不会影响到对方
  • 对于“具体的抽象类”所做的改变,不会影响到客户

用途:

  • 适合使用在需要跨越多个平台的图形和窗口系统上
  • 当需要用不同方式改变接口和实现时

缺点:

  • 增加了复杂度

生成器模式 Builder

书中例子:制定一套度假计划,每天都有吃饭、住房(但是本地居民不需要)、活动


生成器模式 封装一个产品的构造过程,并允许按步骤构造


优点:

  • 将一个复杂对象的创建过程封装起来
  • 允许对象通过多个步骤来创建,并且可以改变过程(工厂模式只有一个步骤)
  • 向客户隐藏产品的内部表现
  • 产品的实现可以被替换,因为客户只看到一个抽象的接口

用途:

  • 经常被用来创建组合结构

缺点:

  • 与工厂模式相比,采用生成器模式创建对象的客户,需要具备更多的领域知识

责任链模式 Chain of Responsibility

书中例子:过滤分类之后的邮件送到对应的部门手中


责任链模式一个以上的对象有机会能够处理某个请求

例如:先某个部门处理它能处理的邮件,剩下的传给下一个部门,一直传到最后就是垃圾邮件了


优点:

  • 将请求的发送者和接受者解耦
  • 可以简化你的对象,因为它不需要知道链的结构
  • 通过改变链内的成员或调动它们的次序,允许你动态地新增或者删除责任

用途:

  • 经常被使用在窗口系统中,处理鼠标和键盘之类的事件

缺点:

  • 并不保证请求一定会被执行,如果没有任何对象处理它的话,它可能会落到链尾端之外(可能也是优点)
  • 可能不容易观察运行时的特征,有碍于除错

蝇量模式 Flyweight

书中例子:房产公司规划树木绿化,树很多,公司一个


蝇量模式让某个类的一个实例能用来提供许多“虚拟实例”


优点:

  • 减少运行时对象实例的个数,节省内存
  • 将许多“虚拟”对象的状态集中管理

用途:

  • 当一个类有许多实例,而这些实例能被同一方法控制的时候

缺点:

  • 单个的逻辑实例将无法拥有独立而不同的行为

解释器模式 Interpreter

书中例子:儿童学习编程工具


解释器模式为语言创建解释器,每个语法规则用一个类代表,类直接映射到语法


优点:

  • 将每一个语法规则表示成一个类,方便实现语言
  • 因为语言由许多类表示,所以你可以轻易地改变或扩展此语言
  • 通过在类结构中加入新的方法,可以在解释的同时增加新的行为

用途:

  • 实现一个简单语言时
  • 有一个简单语法,而且简单比效率更重要时

缺点:

  • 当语法规则的数目太大时,这个模式会变得非常复杂(这种情况下,使用解释器/编译器的产生器可能更合适)

中介者模式 Mediator

书中例子:未来屋的每天闹钟不同事例的提醒


中介者模式来集中相关对象之间复杂的沟通和控制方式,对象之间彻底解耦,中介者内包含了整个系统的控制逻辑


  • 每个对象都会在自己的状态改变时,告诉中介者
  • 每个对象都会对终结者所发出的请求作出回应

优点:

  • 通过将对象彼此解耦,可以增加对象的复用性
  • 通过将控制逻辑集中,可以简化系统维护
  • 可以让对象之间所传递的消息变得简单而且大幅减少

用途:

  • 常常被用来协调相关GUI组件

缺点:

  • 如果设计不当,中介者对象本身会变得过于复杂

备忘录模式 Mememto

书中例子:游戏的存储进度功能


备忘录模式可以让对象返回之前的状态(“撤销”)

备忘录模式有两个目标

  • 储存系统关键对象重要状态
  • 维护关键对象封装

注:单一责任原则,不要把保持状态的工作和关键对象混在一起,专门掌握状态的对象,就称为备忘录;在JAVA系统中,可以考虑使用序列化机制储存系统的状态


优点:

  • 将被储存的状态放在外面,不要和关键对象混在一起,只可以帮助内聚
  • 保持关键对象的数据封装
  • 提供了容易实现的恢复能力

用途:

  • 备忘录用于储存状态

缺点:

  • 储存和恢复状态的过程可能相当耗时

原型模式 Prototype

书中例子:设计怪兽随着场景的变换而变化的游戏


原型模式使用在创建给定类的实例的过程很昂贵或很复杂时,通过复制现有的实例来创建新的实例


优点:

  • 向客户隐藏制造新实例的复杂性
  • 提供让客户能够产生未知类型对象的选项
  • 在某些环境下,复制对象比创建新对象更有效

用途:

  • 在一个复杂的类层次中,当系统必须从其中的许多类型创建新对象时

缺点:

  • 对象的复制有时相当复杂

访问者模式 Visitor

书中例子:在餐厅点菜的时候询问营养信息


访问者模式使用在需要为一个对象的组合增加新的能力,且封装并不重要时

访问者通过导游对象的引导,收集组合中所有对象的状态


优点:

  • 允许对组合结构加入新的操作,而无需改变结构本身
  • 想要加入新的操作,相对容易
  • 访问者所进行的操作,其代码是集中在一起的

缺点:

  • 打破组合类的封装
  • 因为游走的功能牵涉其中,对组合结构的改变就更加困难

后记

2019.02.27

最近开始重拾英语了,也就是看看之前六级买的资料,好多资料都没看呢!

希望能坚持下去,让我的英语水平恢复到正常点的状态呗!

然后瞎看看书,毕竟被机器学习抛弃了呢!

那还是多看看书,也许会发现什么呢!

然后呢,就是我的权律师和吴真心啦~

2019.03.02

javascript看的差不多了,但好像感觉很粗浅,需要再看看应用实例;然后好像我的顺序不对,html,css还不会呢!

之后估计还要看看面向对象概念,然后再看看算法,数据结构啥的

然后还要看看面试会问哪些方面的内容

然后我还要练字

emmm……还要好好玩

感觉事情好多呢!

2019.03.03

手机掉水里了,好难过

然后好像面试要问学过的课程

emmm……今天睡了好久

2019.03.05

今天上课,发现真的不喜欢这个老师的课

感觉都是在讲历史故事

emmm……

然后看到周围同学在准备六级、考研、雅思、pat、算法……

emmm……我要好好看完这个模式,然后也要写点代码了

不然给xxx丢脸了

2019.03.06

模式好多呢

晕晕乎乎

2019.03.10

有先见之明的我,提前两个月开始准备礼物了

因为一千片的星空拼图啊(分完6类后,头都大了

希望我能如期完成吧!

然后我们从平遥换到成都的清明旅游啦

要准备攻略啦~

2019.02.19

拼图完成了四分之一了,发现有些片缺少了呢

记一下设计模式的其他经典书籍,四人组的《Design Patterns——Elements of Reusable Object-Oriented Software》

希望我销卡顺利 卖书顺利(还有好多书没看过呢(._.))

转载请注明出处,谢谢。

愿 我是你的小太阳

买糖果去喽