设计模式

设计原则

单一职责原则

单一职责原则(Single Responsibility Principle, SRP)是一个备受争议的原则。

考虑下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
IUserInfo 
+ setUserID
+ getUserID
+ setPwd
+ getPwd
+ setUserName
+ getUserName
+ changePwd
+ deleteUser
+ mapIser
+ addOrg
+ addRole

这个接口设计显然存在很大问题,因为用户信息和行为被混淆在了一起。正确的做法是分成两个接口:

1
2
3
4
5
6
7
IUserBO
+ setUserID
+ getUserID
+ setPwd
+ getPwd
+ setUserName
+ getUserName
1
2
3
4
5
6
IUserBiz
+ changePwd
+ deleteUser
+ mapIser
+ addOrg
+ addRole

但这个设计很多时候需要考虑实际情形,比如对于一个电话,合理的设计是这样的:

1
2
3
4
5
6
IConnectionManager
+ dial
+ hangup
IDataTransfer
+ DataTransfer
Phone IMPLEMENTS IConnectionManager, IDataTransfer

但这样可能代价也会比较昂贵。

同样对于方法,我们也应该使用正确的处理。比如

1
+ changeUser(IUserBO userBO, String.. changeOptions)

这个方法就应该拆解成一系列方法:

1
2
3
+ changeUserName(IUserBO userBO, String userName)
+ changeHomeAddress(IUserBO userBO, String address)
......

里氏替换原则

对于继承,一直就是毁誉参半。其优点在于:提高重用性,提高扩展性,提高开放性。但另一方面,继承是侵入性的,会降低子类的灵活性,同时也会增强耦合性。如何判定一个继承的合理性?我们使用里氏替换原则(Liskov Substitution Principle, LSP)。

定义1

$\forall, o \in \text{type} , A$,$\exists p \in , \text{type} ,B$,使得$T$定义的程序$P$在所有对象$o$换成$p$的时候,$P$的行为不发生变化,那么$S$是$T$的子类型

定义2

所有引用基类的地方必须能透明的使用子类的对象

这里要考虑三个要求。

(1)子类必须完全实现父类方法

我们考虑如下的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
classDiagram
class Client
class Soldier {
-AbstractGun gun
+void setGun(AbstractGun gun)
+void killEnemy()
}
class AbstractGun {
+void shoot()
}
class HandGun
class Rifle
class MachineGun
Client --> Soldier
Client --> AbstractGun
AbstractGun *-- Soldier
AbstractGun <|-- HandGun
AbstractGun <|-- Rifle
AbstractGun <|-- MachineGun

这看上去是个很完美的逻辑。但如果在此基础上加入一个坏枪,继承AbstractGun,不得不实现一个shoot方法,而此时我们的Soldier类的killEnemy方法就显得非常怪诞。

那怎么办?一种做法是基于jdk17新的模式匹配:

1
2
3
4
switch (p) {
case BadGun b -> break;
default -> // do something...
}

但这样的工作量是比较大的,而且会让代码结构变得混乱。所以我们把物体从继承流中抽离出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
classDiagram
class Client
class Soldier {
-AbstractGun gun
+void setGun(AbstractGun gun)
+void killEnemy()
}
class AbstractGun {
+void shoot()
}
class HandGun
class Rifle
class MachineGun
class AbstractBad
class BadGun
Client --> Soldier
Client --> AbstractGun
AbstractGun *-- Soldier
AbstractGun <|-- HandGun
AbstractGun <|-- Rifle
AbstractGun <|-- MachineGun
AbstractBad --* AbstractGun
AbstractBad <|-- BadGun

这里我们建立一种关联委托关系,将枪的外形赋予AbstractGun处理。

(2)子类可以有自己的个性

这个不用多说,向下转型不安全。

(3)覆盖或实现父类方法时,参数可以放大

考虑下面的两个类:

1
2
3
4
5
6
7
8
9
10
class A {
public void f(HashMap m) {
//...
}
}
class B extends A {
public void f(Map m) {
//...
}
}

B::f(Map)是对B::f(HashMap)的一个Overload。考虑下面的场景:

1
2
3
static void func() {
new B().f(new HashMap());
}

这个时候,由于java的类型机制,调用的是父类的f方法。根据里氏替换原则,我们将B换成A,会发现行为没有发生变化。

但假如反过来:

1
2
3
4
5
6
7
8
9
10
class A {
public void f(Map m) {
//...
}
}
class B extends A {
public void f(HashMap m) {
//...
}
}

此时,new B(new HashMap())调用的是B::f(HashMap),但如果将B换成Anew A(new HashMap())调用的就是A::f(Map)

没有override父类方法的前提下,子类方法被执行了!

(4)覆写或实现父类方法时输出结果可以被所缩小

如果父类方法返回值是$T$,子类相同方法返回值是$S$,那么$S \le T$。

总而言之,里氏替换原则需要尽量避免子类个性,同时也要有一定的个性存在。

依赖倒置原则

依赖倒置原则包括几个含义:

  • 高层模块不依赖底层模块,两者都依赖其抽象
  • 抽象不应该依赖细节
  • 细节应该依赖抽象

不可分割的子逻辑就是低层模块,组合起来就是高层模块。但是如何理解抽象和细节?在java中,抽象是接口或抽象类,不可直接实例化;细节就是实现类。因此,从java的表现来说,就是:

  • 模块间的依赖通过抽象发生,实现类之间没有明确的依赖关系
  • 接口或抽象类不依赖于实现类
  • 实现类依赖接口或抽象类

考虑下面一个例子:

1
2
3
4
5
class Driver
+ void driver(Benz)
class Benz
+ void run()
class Client

Driver::driver(Benz)方法调用Benz::run()。现在问题来了,如果我们又来了一个宝马呢?所以问题出现了。因此,我们将依赖倒置过来:

1
2
3
4
5
interface ICar
class Benz : ICar
class BMW : ICar
class Driver
+ void driver(Car)

因此,这一原则可以减少类耦合

同时,这样还可以带来一个好处:并行开发。如果我们需要调用Driver::driver()方法,但Car的类设计是由另一个人完成的,此时,任务完全无法运行。但如果我们传入一个接口,现在完全可以进行下面的单元测试:

1
2
3
4
5
6
7
8
9
10
11
12
public class DriverText extends TestCase {
var context = new JUnit4Mockery();
@Test
public void testDriver() {
final ICar car = context.mock(ICar.class);
IDriver driver = new Driver();
context.checking(new Exceptions(){{
oneOf(car).run();
}});
driver.drive(car);
}
}

这样就可以解除依赖进行单元测试了。

接下来给出依赖的三种写法:

构造函数传递依赖对象

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface IDriver {
public void driver();
}

public class Driver implements IDriver {
private ICar car;
public Driver(ICar _car) {
this.car = _car;
}
public void drive() {
this.car.run();
}
}

Setter依赖注入

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface IDriver {
public void driver();
}

public class Driver implements IDriver {
private ICar car;
public void setCar(ICar _car) {
this.car = _car;
}
public void drive() {
this.car.run();
}
}

接口声明依赖

1
2
3
4
5
6
7
8
9
10
public interface IDriver {
public void driver();
}

public class Driver implements IDriver {
private ICar car;
public void drive(ICar car) {
this.car.run();
}
}

在项目中使用,要遵循几个规则:

  • 每个类都有接口或抽象类
  • 变量的表面类型都尽量是接口或抽象类
  • 任何类不应该从具体类派生。当然,实际上一般不超过两层的继承是可以接受的。
  • 尽量不要覆写积累方法
  • 结合里氏替换原则

接口隔离原则

我们先定义接口。

  • 实例接口 用new实例化一个类,例如var A = new C(),那么C就是A的接口
  • 类接口 用interface定义的接口

那什么是隔离呢?

  • 客户端不应该依赖它不需要的接口
  • 类间的依赖关系应该建立在最小接口上

也就是说,接口要尽可能细化。

这个原则和单一职责有点像,不过它更体现在不同模块上。书上举的例子是美女,感觉不太好,下面这个图写的比较清楚:

image-20210928164417580

这种设计非常混乱,我们可以进行优化:

image-20210928164449835

这里的核心是如何设计接口,有几个要求:

  • 接口尽可能小,但建立在单一职责基础之上。
  • 接口要高内聚,尽可能减少public方法。
  • 考虑定制服务。
  • 也不能太小。

这里再来介绍一个例子。有一个接口如下:

1
2
3
4
5
IBookSearcher
+ searchByName
+ searchByTitle
+ searchByPublisher
+ complexSearch

某一日突然发现屎山跑起来很慢,结果发现complexSearch导致。这个方法本身不应该被公网所调用,但无意之中公布了出去。所以解决方法就是将接口拆分:

1
2
3
4
5
6
ISimpleBookSearcher
+ searchByName
+ searchByTitle
+ searchByPublisher
IComplexBookSearcher
+ complexSearch

迪米特法则

一个对象应该被其它对象最少了解。也就是一个类对自己需要调用的类了解的应该最少。这体现出一种类的低耦合。

(1)只与直接朋友通信。举一个例子:考虑一个玩家的上子弹操作。

我们可能会这么实现:

1
2
List<Bullet> bullets = new List<Bullet>();
this.gun.addBullet(bullets);

但想一想,玩家和子弹之间是没有操作可言的。换言之,我们这样定义朋友类:出现在成员变量、方法的输入输出参数中的类是朋友类。这里的Bullet并不是朋友类,但我们在方法中使用了一个非朋友类,这就破坏了封装性。

所以比较好的解决方法是,把Bullet的依赖改到gun上:

1
2
3
4
5
//Player::addBullet
this.gun.addBullet(10);
//Gun::addBullet
List<Bullet> bullets = new List<Bullet>(cnt);
...

(2)控制适当距离。

考虑下面的例子:考虑一个手雷的类

1
2
3
4
class Bomb
+ openLock
+ throwOut
+ boob

那么我们投掷一个手雷的过程如下:

1
2
3
4
5
6
var bomb = GetComponent<Bomb>();
if (bomb.openLock()) {
if (bomb.throwOut()) {
bomb.boob();
}
}

但这个逻辑问题很大。比如说,我们现在给手雷做了一次升级,给手雷多了一道保险,需要进行结印才能爆炸。这就意味着我们需要把所有类的相关方法全部改一遍,这显然是不能接受的。

所以正确的做法是,我们将bomb类进行修改:

1
2
3
4
5
class Bomb
- openLock
- throwOut
- boob
+ makeAttack

(3)自己的属性随便改

(4)谨慎使用Serializable

因此,迪米特法则的核心就是高内聚与低耦合的统一。

开闭原则

一个软件实体如类、模块、函数应该对扩展开放,对修改关闭。

首先,什么是软件实体?软件实体包括项目或软件产品按照一定逻辑规则划分的模块、抽象和类、以及方法。开闭原则告诉我们要尽量通过扩展软件实体的行为来实现变化,形成一种约束原则。

考虑下面这个例子:

1
2
3
4
5
6
7
8
IBook
+ getName()
+ getPrice()
+ getAuthor()
NovelBook Extends IBook
+ name
+ price
+ author

现在这个逻辑没有任何问题。假如我们有一个BookStore类:

1
2
3
4
5
6
7
8
9
class BookStore {
var bookList : List<IBook> = listof(
NovelBook('CSAPP', '108', 'aaa'),
NovelBook('DP', '80', 'bbb')
)
fun main() {
//...
}
}

现在问题突然出现了:书滞销,需要打折。有几种方法可以实现:

(1)修改接口:在IBook上增加一个getOffPrice(),但这意味着继承层次中所有类都需要实现这个接口。

(2)修改实现类:在getPrice()中直接打折。但这样比较麻,因为有的时候我们希望获取到原价。

(3)继承。如果我们增加一个子类OffNovelBook,然后override getPrice方法,而我们只需要改变静态生成代码段中的接口,这一部分属于顶层逻辑。

总结一下,变化有三种:

  • 逻辑变化 只变化一个逻辑,一般直接修改原类中的方法
  • 子模块变化 一个模块变化,对其它模块产生影响,尤其是低层模块到高层模块的影响,通过扩展。
  • 可见视图变化 尤其是业务耦合变化,比如前端的数据显示,想从5列变成6列。如果设计的好也可以通过扩展来完成。

在实际业务中,尽可能不要修改历史,尤其是在运维阶段。

为什么开闭原则非常重要?我们从几个角度来谈。

对测试的影响 假如我们对一个类进行了测试,现在修改了这个类的方法,那么原来的测试逻辑很可能出现大问题。换言之,类的所有测试方法都需要重构,这是非常可怕的工作量。对于上面的例子,我们可以这样处理测试:

1
2
3
4
5
6
public class OffNovelBookTest extends TestCase {
private IBook book1 = new OffNovelBook('qwq', 2, 'qwq');
public void testGetPriceBelow40() {
assertEquals(2, book1.getPrice());
}
}

这样的单元测试的孤立的。

对复用性的影响 逻辑是从原则逻辑组合而成的,而不是一个类独立实现的。提高复用性有助于重构和代码管理。

对可维护性的影响 尽可能基于扩展而不是修改源代码来完成工作

面向对象开发的要求 设计之初考虑“变化”

如何实现开闭原则呢?提供四点建议。

抽象约束 接口或抽象类的约束扩展,对扩展进行边界限制;参数类型、引用对象使用接口类;抽象层保持稳定。

元数据控制模块行为 尽可能多的使用模块数据,极致就是控制反转。

制定项目规章 约定优于配置,例如指定章程“所有Bean都自动注入,使用Annotation装配”。

封装变化 将相同的变化封装到接口或抽象类中,将不同的变化封装到不同的接口和抽象类中。

构建型模式

单例模式

单例模式用的已经非常多了。

最基本的单例模式

1
2
3
4
5
6
7
public class C {
private static final C c = new C();
private C() { }
public static C getInstance() {
return c;
}
}

其优缺点是怎样的呢?

  • 单例模式在内存中只有一个实例,减少了内存开支
  • 防止对资源的多重占用
  • 系统设置全局的访问点
  • 没有接口,难以扩展,破坏OO
  • 不利于单元测试
  • 与单一职责原则冲突

我们上面给出的单例是线程安全的。如果c是一个lateinit对象,就有可能出现线程安全性问题。这种时候可以加上synchronized。此外,一般单例不实现Cloneable接口。

固定数量单例

可以多生成几个单例。

1
2
3
4
5
6
7
8
9
10
11
12
public class C {
private static int instanceCount = 2;
private static ArrayList<C> instanceList = new ArrayList<C>();
static {
for (int i = 0; i < instanceCount; ++i)
instanceList[i] = new C();
}
private C() {}
public static getInstance(int id) {
return instanceList[id];
}
}

工厂模式

一般工厂模式

先给出一种默认实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 工厂类接口
public abstract class AbstractFactory implements IProduct {
public abstract <T extends IProduct> T createProduct(Class<T> c);
}

// 工厂类的实现
public class ClassCFactory extends AbstractFactory {
public <T extends IProduct> T createProduct(Class<T> c) {
T product = null;
try {
product = c.getDeclaredConstructor().newInstance();
} catch(Exception e) {
e.printStackTrace();
}
}
}

这种方法的好处在于,我们创建一个对象只需要知道类名和条件约束,而可以忽略具体过程。这样极大的降低了模块之间的耦合。同时,我们在增加产品类的时候,只需要修改对应工厂。

这种方法还屏蔽了产品类,作为调用者不必关心产品类本身,只需要关心产品的接口是怎样的。

工厂方法是new一个对象的替代品,所有需要生成对象的地方都可以使用,但是是否值得以代码复杂度为代价,这就需要具体问题具体分析。

简单工厂模式

取消掉抽象类,并变动态方法为静态方法。

1
2
3
4
5
6
7
8
9
10
public class SimpleFactory {
public static <T extends IProduct> createProduct(Class<T> c) {
T product = null;
try {
product = c.getDeclaredConstructor().newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
}

多工厂模式

刚才我们用Instance来构造类,可以对一种产品构建一个工厂类。

1
2
3
4
5
6
7
8
9
10
11
12
interface IProduct
class ProductA : IProduct
class productB : IProduct
class ProductC : IProduct
abstract class AbstractFactory
+ abstract create
class ProductAFactory
+ create
class ProductBFactory
+ create
class ProductCFactory
+ create

这种模式的好处是创建类的职责很清晰,结构也很简单,但是一个产品类对应一个工厂类的架构过于复杂。在复杂应用中,可以用多工厂+协调类的方法。

替代单例模式

我们可以用工厂构造一个通用的单例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Singleton {
private Singleton() {}
public void work() {}
}

public class SingletonFactory {
private static Singleton singleton;
static {
try {
Class clazz = Class.forName(Singleton.class.getName());
Constructor constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
singleton = (Singleton)constructor.newInstance();
}
}

public static Singleton getSingleton() {
return singleton;
}

}

这样的好处是,我们构造了一个工厂,对于确定的类型,只产生一个实例。

延迟初始化

考虑下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ProductFactory {
private static final Map<String, IProduct> prMap = new HashMap<>();
public static synchronized IProduct createProduct(String type) throws Exception {
IProduct product = null;
if (prMap.containsKey(type)) {
return prMap.get(type);
} else {
if (type.equals("type1")) {
product = new ProductA();
} else {
product = new ProductB();
}
prMap.put(type, product);
}
return product;
}
}

可以看成扩展的单例模式。

抽象工厂模式

直接上模板:

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
interface IProduct {
fun work()
}

abstract class AbstractProductA : IProduct {
fun sharedMethodA() {}
}

abstract class AbstractProductB : IProduct {
fun sharedMethodB() {}
}

class ProductA1 : AbstractProductA() {
override fun work() {
TODO("Not yet implemented")
}
}

class ProductA2 : AbstractProductA() {
override fun work() {
TODO("Not yet implemented")
}
}

class ProductB1 : AbstractProductB() {
override fun work() {
TODO("Not yet implemented")
}
}

class ProductB2 : AbstractProductB() {
override fun work() {
TODO("Not yet implemented")
}
}

abstract class AbstractCreator {
abstract fun createProductA() : AbstractProductA
abstract fun createProductB() : AbstractProductB
}

class Creator1 : AbstractCreator() {
override fun createProductA(): AbstractProductA {
return ProductA1()
}

override fun createProductB(): AbstractProductB {
return ProductB1()
}
}

class Creator2 : AbstractCreator() {
override fun createProductA(): AbstractProductA {
return ProductA2()
}

override fun createProductB(): AbstractProductB {
return ProductB2()
}
}

抽象工厂模式具有非常强的封装性。一个非常经典的应用场景是,我们有Win Linux和Android三套API,这个时候设计三套接口就可以用抽象工厂来屏蔽操作系统对应用的影响。

因此,这种模式用在一个对象族相同的约束,并且这种约束可以保证一种非公开状态。但是抽象工厂模式的缺点在于扩展非常难,因为新的对象族的引入代表大量类需要重构。

建造型模式

这个也很常用了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Product {
fun work() {}
}

abstract class Builder {
abstract fun setPart()
abstract fun build() : Product
}

class ConcreteProduct : Builder() {
private var product = Product()
override fun setPart() {
TODO("Not yet implemented")
}

override fun build(): Product {
return product
}
}

这种模式适合相同方法、不同执行顺序,或是多个部件的装配。这里要注意,它常常和模板方法一起使用。下面给出了一个例子:

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
abstract class SpellCard {
abstract fun playEffect()
abstract fun makeAttack()
abstract fun startTimer()
var constructList = listOf<Int>()
fun run() {
constructList.stream().forEach {
when(it) {
1 -> playEffect()
2 -> makeAttack()
3 -> startTimer()
}
}
}
}

class Card1Builder(sequence: List<Int>) : CardBuilder(sequence) {
private var card = Card1()
init {
card.constructList = sequence
}
override fun getSpellCard(): SpellCard {
return card
}
}

class Card2Builder(sequence: List<Int>) : CardBuilder(sequence) {
private var card = Card2()
init {
card.constructList = sequence
}
override fun getSpellCard(): SpellCard {
return card
}
}

原型模式

这个东西比较简单,就是clone方法。先创建一个比较重要的类,然后clone之,并修正细节信息。可是为什么我们需要clone呢?有如下几个原因:

  • 性能好。原型模式通过二进制流拷贝,比new对象性能好很多。
  • 逃避构造函数约束。无需构造函数调用过程。
  • 绕过安全约束和访问权限控制。
  • 一个对象多个修改者或涉及到多线程。

这里还要注意经典的深拷贝与浅拷贝的问题。引用的成员变量有两个条件不会被拷贝:

  • 类的成员变量
  • 可变的引用对象,而不是原始类型或String或不可变对象

这里简单说一下cpp的原型模式怎么实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Base {
public:
std::unique_ptr<Base> clone() { return std::make_unique<Base>(*mClone()); }
private:
virtual Base* mClone() { return new Base(*this); }
}

class Derived : public Base {
public:
std::unique_ptr<Derived> clone() { return std::make_unique<Base>(*mClone()); }
private:
virtual Derived* mClone() { return new Derived(*this); }
}

这种方法可以回避由于不知道类型而无法调用copy constructor。

结构型模式

代理模式

代理模式非常非常重要。

静态代理

这样可以实现一个最基本的代理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface Subject {
fun request()
}

class RealSubject : Subject {
override fun request() { }
}

class Proxy(val subject: Subject) : Subject {
fun before() {}
fun after() {}
override fun request() {
before()
subject.request()
after()
}
}

这样,我们把真实的活动委托到一个代理类执行。

普通代理

普通代码在此基础上又进行了一层遮蔽。原来的代理需要知道代理对象是谁,但是有的时候我们甚至连这点东西都不希望调用者知道。解决方法也很简单,把构造过程放在Proxy类中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface Subject {
fun request()
}

class RealSubject(var name : String) : Subject {
override fun request() { }
}

class Proxy(var name : String) : Subject {
val subject = RealSubject(name)
fun before() {}
fun after() {}
override fun request() {
before()
subject.request()
after()
}
}

强制代理

正好反过来,用真实角色管理代理。

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
46
47
interface Subject {
fun request()
fun getProxy() : Subject
}

class RealSubject : Subject {

var mProxy : Subject? = null
fun isProxy() : Boolean {
if (mProxy == null) return false
return true
}

override fun getProxy(): Subject {
if (mProxy == null) mProxy = MProxy(this)
return mProxy!!
}

override fun request() {
if (isProxy()) {
print("work")
} else {
print("Not Work")
}
}

}

class MProxy(var subject : Subject) : Subject {
fun before() {}
fun after() {}
override fun request() {
before()
subject.request()
after()
}

override fun getProxy(): Subject {
return this
}
}

fun main() {
val sub = RealSubject()
val subProxy = sub.getProxy()
subProxy.request()
}

动态代理

这一部分是重头戏。我们用动态代理往往可以实现AOP(面向横切面编程,Aspect Oriented Programming)。

笑死,根本看不懂

先扔不知道为啥跑不了的代码。。

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
46
47
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method
import java.lang.reflect.Proxy

interface Subject {
fun work(str : String)
}

class RealSubject : Subject {
override fun work(str: String) {
print("real subject::work")
}
}

class MyInvocationHandler(var target : Any) : InvocationHandler {
override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any {
return method?.invoke(target, args)!!
}
}

class DynamicProxy<T> {
companion object {
fun <T> newProxyInstance(loader: ClassLoader, interfaces: Array<Class<out Any>>, h : InvocationHandler) : T {
if (true) {
BeforeAdvice().exec()
}
return Proxy.newProxyInstance(loader, interfaces, h) as T
}
}
}

interface IAdvice {
fun exec()
}

class BeforeAdvice : IAdvice {
override fun exec() {
println("Before Exec")
}
}

fun main() {
val subject : Subject = RealSubject()
val handler : InvocationHandler = MyInvocationHandler(subject)
val proxy = DynamicProxy.newProxyInstance<Subject>(RealSubject::class.java.classLoader, RealSubject::class.java.interfaces, handler)
proxy.work("233")
}

装饰模式

装饰模式的作用是在工作类的外部扩展工作类的功能。看看代码:

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
abstract class Component {
abstract fun operate()
}

class ConcreteComponent : Component() {
override fun operate() {
println("qwq")
}
}

abstract class Decorator(private val component: Component) : Component() {
override fun operate() {
component.operate()
}
}

class Decorator1(private val component: Component) : Decorator(component) {
private fun decWork() {
println("dec1")
}

override fun operate() {
decWork()
super.operate()
}
}

class Decorator2(private val component: Component) : Decorator(component) {
private fun decWork() {
println("dec2")
}

override fun operate() {
super.operate()
decWork()
}
}

fun main() {
var component : Component = ConcreteComponent()
component = Decorator1(component)
component = Decorator2(component)
component.operate()
}

这样可能比较难理解,我们看看发生了什么:

1
2
3
4
5
6
7
component.operate()  // component : Decorator2
super.operate()
component.operate() // component : Decorator1
decWork() // "dec1"
super.operate()
component.operate() // component : ConcreteComponent, "qwq"
decWork() // "dec2"

可以看到,比起传统的用继承来实现复用来说,装饰类提供了一种不加以耦合而从外部扩展功能的方案。可以认为,继承是静态的增加功能,装饰是动态的增加功能,并且非常易于扩展。

适配器模式

类适配器

适配器就是一个转换头,从一种接口改变成另一种接口。

这里有些概念比较容易混淆。我们先画个图:

1
2
3
4
111111111111         111111111                   11111111111  
1111111111111111 11111111111111 11
1111111111111111 11111111111111 11
111111111111 111111111 11111111111

左右两边不匹配,所以用一个新的物体来适配。这里有三个基本元素:

  • Target 目标角色,也就是把其它类转化成谁。
  • Adaptee 源角色,也就是把谁转化成目标角色
  • Adapter 适配器

我们先给出一个比较简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface ITarget {
fun work()
}

class ConcreteTarget : ITarget {
override fun work() {
println("目标角色,勿动!")
}
}

open class ConcreteAdaptee {
open fun work2() {
println("源角色,勿动!")
}
}

class Adapter : ConcreteAdaptee(), ITarget {
override fun work() {
super.work2()
}
}

这个看上去好像没什么问题,很简单。这种通过继承关系构造的适配器,叫做类适配器。

对象适配器

但是问题来了:如果我们的原角色不是一个类,而是多个类,怎么办?把原有适配器的继承关系变成关联关系,然后让适配器对多个类进行适配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class ConcreteAdapteeA  {
fun work2() {
println("源角色1,勿动!")
}
}

class ConcreteAdapteeB {
fun work3() {
println("源角色2,勿动!")
}
}

class Adapter(val adA : ConcreteAdapteeA, val adB : ConcreteAdapteeB) : ITarget {
override fun work() {
adA.work2()
adB.work3()
}
}

这种利用对象合成关系构造的适配器叫做对象适配器,具有更强的泛用性。

组合模式

组合模式实际上是建立一颗树,并对其进行遍历。

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
// 节点
abstract class Component {
open fun work() {
println("Component!")
}
}

// 树枝
class Composite : Component() {
var componentList = ArrayList<Component>()
private set
fun add(component: Component) {
componentList.add(component)
}
fun remove(component: Component) {
componentList.remove(component)
}
}

// 叶子
class Leaf : Component() {
override fun work() {
println("Leaf!")
}
}

fun go(root : Composite) {
root.componentList.forEach {
if (it is Leaf) {
it.work()
} else {
go(it as Composite)
}
}
}

这样的一棵树开闭比较容易,简化了实现,但是在使用的时候需要对树根进行遍历,所以违反了依赖倒置原则。这方面主要是数据结构的范畴,这里不做过多讨论。

门面模式

一种常见的封装模式,提供一个高层次的接口让子系统更易于使用。一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class A {
fun a(){}
}

class B {
fun b(){}
}

class C {
fun c(){}
}

class Facade {
val a = A()
val b = B()
val c = C()
fun f1() { a.a() }
fun f2() { b.b() }
fun f3() { c.c() }
}

门面模式相当于一层多出来的封装,减少了系统相互依赖,提高了灵活性和安全性,但不符合开闭原则。一般用在子系统相对独立的情景。

门面模式在使用的时候,要特别注意不参与子系统的内部逻辑。比如,我们可能写这一这一的f3:

1
fun f3() { a.a(); c.c(); }

这种行为相当于把内在逻辑蕴含在了门面中,违反了单一职责原则。对于这种情况,建议新建一层逻辑类进行封装。

享元模式

由于GC具有不确定性,可以自己定义一个轻量级的池容器。

享元模式有两个要求:细粒度对象共享对象

为了实现细粒度,我们把对象状态分成两类:

  • 内部状态:不同对象具有的属性
  • 外部状态:区别不同对象的标识

下面是一个实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
abstract class Flyweight(val extrinsic : String) {
var intrinsic : String = "" //内部状态
abstract fun operate()
}

class ConcreteFlyweight1(extrinsic: String) : Flyweight(extrinsic) {
override fun operate() {
print("c1")
}
}

class FlyweightFactory {
companion object {
var pool = mutableMapOf<String, Flyweight>()
fun getFlyweight(extrinsic: String) : Flyweight {
return pool.getOrElse(extrinsic, {
val t = ConcreteFlyweight1(extrinsic)
pool[extrinsic] = t
t
})
}
}
}

这里还要说明一下。享元模式和对象池模式的区别是什么?享元模式的核心是“享”,也就是对共通部分的复用。考虑这样一个应用场景:有100万考生报考,需要收集姓名、身份证号、考点、科目。但是考点和科目只有固定的几百个,那这一百万考生完全有可能简化到几百个类里,考虑并发的话最多几千个。

对象池解决的问题则不太相同,主要目的是防止对象的实例化,因为实例化的开销比较大,这样可以优化内存管理状态。

桥梁模式

这个模式也是一个不知道名字的大路货,它的核心就是“把抽象和实现解耦”。先看代码:

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
interface Implementor {
fun work1()
fun work2()
}

class Implementor1 : Implementor {
override fun work1() {
print("i1 work1")
}

override fun work2() {
print("i2 work2")
}
}

class Implementor2 : Implementor {
override fun work1() {
print("i2 work1")
}

override fun work2() {
print("i2 work2")
}
}

abstract class Abstraction(val imp : Implementor) {
open fun req() {
imp.work1()
}
}

class RefinedAbstraction(imp: Implementor) : Abstraction(imp) {
override fun req() {
super.req()
super.imp.work2()
}
}

fun main() {
RefinedAbstraction(Implementor1()).req()
}

可以看到,我们把实现注入到抽象中,然后使用模板方法来调用,这样抽象和实现都可以自己进行改变,而无需引起过多变化。

桥梁模式描述的这种弱关联关系非常方便,比如A有一个方法f,B继承A,C继承B。这个时候B的方法f不能随意改写,但是如果把f分离到一个实现中,就可以随意注入。

过程型模式

模板方法模式

基本情形

这是一个常用到不怎么知道叫做模板方法的模式。

考虑下面的例子:

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
46
abstract class SpellCard {
abstract fun playEffect()
abstract fun makeAttack()
abstract fun startTimer()
abstract fun run()
}

class Card1 : SpellCard() {
override fun playEffect() {
TODO("Not yet implemented")
}

override fun makeAttack() {
TODO("Not yet implemented")
}

override fun startTimer() {
TODO("Not yet implemented")
}

override fun run() {
playEffect()
makeAttack()
startTimer()
}
}

class Card2 : SpellCard() {
override fun playEffect() {
TODO("Not yet implemented")
}

override fun makeAttack() {
TODO("Not yet implemented")
}

override fun startTimer() {
TODO("Not yet implemented")
}

override fun run() {
playEffect()
makeAttack()
startTimer()
}
}

问题在哪?run()方法冗余。所以我们完全可以直接修改抽象类:

1
2
3
4
5
6
7
8
9
10
abstract class SpellCard {
abstract fun playEffect()
abstract fun makeAttack()
abstract fun startTimer()
fun run() {
playEffect()
makeAttack()
startTimer()
}
}

这种将在基类中定义运行框架,而在子类中延迟实现的方法叫做模板方法模式。它有几个要素:

  • 基本方法 子类实现的方法,在模板方法调用
  • 模板方法 一个或多个具体方法,对基本方法调用
  • 具体模板 实现父类定义的一个或多个抽象方法的子类

模板方法的优越性在于对不变部分的封装和可变部分的扩展,也提高了代码的复用性。

Hook Function

假如我们想判断是不是魔炮,从而考虑是否加入抖屏特效,怎么办?可以做如下修改:

1
2
3
4
5
6
7
8
abstract fun isShake() : Boolean
fun run() {
if (isShake()) {
playEffect()
}
makeAttack()
startTimer()
}

然后在子类中:

1
2
3
4
5
var shake : Boolean = false

override fun isShake(): Boolean {
return shake
}

类似于这种通过函数来控制模板方法行为的函数,叫做钩子函数

中介者模式

很好理解。假如A、B、C相互之间有互相调用的需求,但直接调用就会结成一张大网,非常弱智。这个时候就可以引入一个中介者Mediator。Mediator需要一个execute方法,根据某个类的指令来调用相关方法。

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
abstract class Colleague (protected val mediator: Mediator)

class ColleagueA(mediator: Mediator) : Colleague(mediator) {
fun mA() {}
fun mB() {}
}

class ColleagueB(mediator: Mediator) : Colleague(mediator) {
fun mA() {}
fun mB() {}
}

abstract class Mediator {
var c1 : ColleagueA? = null
var c2 : ColleagueB? = null
abstract fun work1()
abstract fun work2()
abstract fun execute(cmd : String)
}

class ConcreteMediator : Mediator() {
override fun work1() {
c1?.mA()
c2?.mB()
}

override fun work2() {
c1?.mB()
c2?.mA()
}

override fun execute(cmd: String) {
when(cmd) {
"launch-B" -> work1()
"launch-A" -> work2()
}
}
}

中介者模式降低了类的耦合,但是也使得自身的逻辑非常庞大。如果出现了蜘蛛网结构,一般可以梳理成星形结构。同时也要谨防中介者被滥用。

命令模式

假设有A,B,C工作类,由D所指挥。那么D就需要同时操控A,B,C,耦合度很高。所以我们可以这样处理:

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
abstract class Reciever {
abstract fun work()
}

class ConcreteRecieverA : Reciever() {
override fun work() {}
}

class ConcreteRecieverB : Reciever() {
override fun work() {}
}

abstract class Command {
abstract fun execute()
}

class CommandA(private val reciever : Reciever) : Command() {
override fun execute() {
reciever.work()
}
}

class CommandB(private val reciever: Reciever) : Command() {
override fun execute() {
reciever.work()
}
}

class Invoker {
var command : Command? = null
fun action() {
command?.execute()
}
}

fun main() {
val invoker = Invoker()
val reciever = ConcreteRecieverA()
invoker.command = CommandA(reciever)
invoker.action()
}

这种传统的命令模式有问题:为什么我们需要知道Reciever呢,这不是违反了迪米特法则吗?所以我们需要对Reciever进行屏蔽。也就是把Reciever的逻辑交到command本身上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
abstract class Command(protected val reciever : Reciever) {
abstract fun execute()
}

class CommandA : Command(ConcreteRecieverA()) {
override fun execute() {
reciever.work()
}
}

class CommandB : Command(ConcreteRecieverB()) {
override fun execute() {
reciever.work()
}
}

命令模式也是一种解耦合,调用者只需要知道execute。同时,如果子类过于膨胀,也可以使用模板方法模式。

责任链模式

责任链模式的核心是链。假如你向区法院提起上诉,一审判决不服,上诉到市法院,一层层升级,这个时候就得考虑一个个级别。我们就需要构造一个链状结构。

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
enum class Level {
LOW_LEVEL,
MEDIUM_LEVEL,
HIGH_LEVEL
}

class Request(val reqLevel : Level)

data class Response(val msg : String)

abstract class Handler(private val nxtHandler : Handler?) {

protected abstract fun getLevel() : Level
protected abstract fun echo(req : Request) : Response

fun handleMsg(req : Request) : Response? {
var res : Response? = null
if (getLevel().equals(req.reqLevel)) {
res = echo(req)
} else {
res = nxtHandler?.handleMsg(req)
}
return res
}
}

class LowHandler(private val handler : MediumHandler) : Handler(handler) {
override fun getLevel(): Level {
return Level.LOW_LEVEL
}

override fun echo(req: Request): Response {
TODO("Not yet implemented")
}
}

class MediumHandler(private val handler : HighHandler) : Handler(handler) {
override fun getLevel(): Level {
return Level.MEDIUM_LEVEL
}

override fun echo(req: Request): Response {
TODO("Not yet implemented")
}
}

class HighHandler : Handler(null) {
override fun getLevel(): Level {
return Level.HIGH_LEVEL
}

override fun echo(req: Request): Response {
TODO("Not yet implemented")
}
}

fun main() {
val high = HighHandler()
val medium = MediumHandler(high)
val low = LowHandler(medium)
var res = low.handleMsg(Request(Level.HIGH_LEVEL))
}

舍弃掉一些无用信息,其实就是根据自己的级别和res的级别来进行递归式的传递。这样,请求者无需知道处理者,处理者也无需知道请求的全貌,就可以完成请求过程。

策略模式

一般策略模式

这个非常简单:

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
interface Strategy {
fun work()
}

class StrategyA : Strategy {
override fun work() {
println("qwq")
}
}

class StrategyB : Strategy {
override fun work() {
println("qaq")
}
}

class Context(val strategy: Strategy) {
fun work() {
strategy.work()
}
}

fun main() {
val st = StrategyA()
val st2 = StrategyB()
var context = Context(st)
context.work()
context = Context(st2)
context.work()
}

我都感觉不知道该说啥,这也叫设计模式.jpg

策略枚举

这种实现方法高度依赖于语言特性。

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
enum class Calculator(val cmd : String) {
ADD("+") {
override fun exec(a: Int, b: Int) : Int {
return a + b
}
},
MINUS("-") {
override fun exec(a: Int, b: Int) : Int {
return a - b
}
},
MULTIPLY("*") {
override fun exec(a: Int, b: Int) : Int {
return a * b
}
},
DIVIDE("/") {
override fun exec(a: Int, b: Int) : Int {
return a / b
}
};
abstract fun exec(a : Int, b : Int) : Int
}

fun main() {
val cmd = "*"
println(enumValues<Calculator>().filter { it.cmd == cmd }.first().exec(3,5))
}

对于其它语言,可能可以用lambda来做【

在实际项目中,我们一般用工厂类来声明策略类,因为我们往往无法知道策略的类型名。

迭代器模式

一个基本已经过时的模式,因为现代语言基本都实现了。懒得写了,没什么意义。。

观察者模式

也叫订阅-发布模式,核心就是被观察者向观察者发送订阅信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
abstract class Subject {
var obs : MutableList<Observer> = mutableListOf()
fun addObserver(observer: Observer) {
obs.add(observer)
}
fun removeObserver(observer: Observer) {
obs.remove(observer)
}
fun notifyAllObserver() {
obs.forEach { it.update() }
}
}

interface Observer {
fun update()
}

class ObserverA : Observer {
override fun update() {
println("handle message...")
}
}

基本没什么说的,观察者和被观察者是抽象耦合,不过也可能有效率问题。使用的时候要注意不能出现消息多次转发,也要考虑异步处理。

在java中,java.util.Observablejava.util.Observer进行了封装。一般来说,我们的观察者update方法一般传递两个参数,一个是被观察者,一个是DTO(数据传输对象)。

备忘录模式

备忘录模式的基本框架

备忘录模式就是对中间状态的一个保存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Originator {
var state = ""
fun createMemento() : Memento {
return Memento(state)
}
fun restoreMemento(memento: Memento) {
state = memento.state
}
}

class Memento(var state : String)

class Caretaker {
var memento : Memento = Memento("")
}

fun main() {
val originator = Originator()
val caretaker = Caretaker()
caretaker.memento = originator.createMemento()
originator.restoreMemento(caretaker.memento)
}

这构成了一个备忘录模式的基本框架,看上去很简单。它尤其适合用在需要回滚的事务上,但要注意它的生命周期管理和性能问题。

基于原型模式的备忘录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Originator : Cloneable {
var state = ""
private var backup : Originator? = null
fun createMemento() {
backup = clone()
}
fun restoreMemento() {
state = backup?.state?:state
}
override fun clone(): Originator {
return super.clone() as Originator
}
}

fun main() {
val originator = Originator()
originator.createMemento()
originator.restoreMemento()
}

把副本存在自己内部,很直接。

多状态备忘录

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class Originator {
var state1 = ""
var state2 = ""
var state3 = ""
fun createMemento() : Memento {
return Memento(backupProp(this))
}
fun restoreMemento(memento: Memento) {
restoreProp(this, memento.stateMap)
}

override fun toString(): String {
return "state1=${state1},state2=${state2},state3=${state3}"
}
}

class Memento(var stateMap : Map<String, Any>)

fun backupProp(bean : Any) : Map<String, Any> {
val res : MutableMap<String, Any> = mutableMapOf()
try {
bean::class.memberProperties.forEach{
val getter = it.getter
val ans = getter.call(bean)
if (ans != null)
res.put(it.name, ans)
}
} catch (e : Exception) {
e.printStackTrace()
}
return res
}

fun restoreProp(bean:Any, propMap : Map<String, Any>) {
try {
bean::class.memberProperties.forEach{
val setter = (it as KMutableProperty1<*, *>).setter
if (propMap.containsKey(it.name))
setter.call(bean, propMap[it.name])
}
} catch (e : Exception) {
e.printStackTrace()
}
}

fun main() {
val ori = Originator()
ori.state1 = "qwq"
ori.state2 = "qaq"
ori.state3 = "quq"
val memento = ori.createMemento()
println(ori)
ori.state1 = "233"
ori.state2 = "hhh"
ori.state3 = "www"
println(ori)
ori.restoreMemento(memento)
println(ori)
}

用反射遍历所有属性,然后存下来。

挺累的,主要不太懂反射库。。

访问权限和多备忘录

由于我们往往不需要自身之外的类访问权限,同时还需要加入多个备忘录,我们可以把代码进一步完善。

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import java.lang.Exception
import kotlin.reflect.KMutableProperty1
import kotlin.reflect.full.*

interface IMemento

class Originator {
var state1 = ""
var state2 = ""
var state3 = ""
fun createMemento() : IMemento {
return Memento(backupProp(this))
}
fun restoreMemento(memento: IMemento) {
restoreProp(this, (memento as Memento).stateMap)
}

override fun toString(): String {
return "state1=${state1},state2=${state2},state3=${state3}"
}

private class Memento(var stateMap : Map<String, Any>) : IMemento
}

class Memento

fun backupProp(bean : Any) : Map<String, Any> {
val res : MutableMap<String, Any> = mutableMapOf()
try {
bean::class.memberProperties.forEach{
val getter = it.getter
val ans = getter.call(bean)
if (ans != null)
res.put(it.name, ans)
}
} catch (e : Exception) {
e.printStackTrace()
}
return res
}

fun restoreProp(bean:Any, propMap : Map<String, Any>) {
try {
bean::class.memberProperties.forEach{
val setter = (it as KMutableProperty1<*, *>).setter
if (propMap.containsKey(it.name))
setter.call(bean, propMap[it.name])
}
} catch (e : Exception) {
e.printStackTrace()
}
}

class CareTaker() {
private var memMap : MutableMap<String, IMemento> = mutableMapOf()
fun getMemento(idx : String) : IMemento? {
return memMap[idx]
}
fun setMemento(idx : String, memento: IMemento) {
memMap[idx] = memento
}
}

fun main() {
val ori = Originator()
val careTaker = CareTaker()
ori.state1 = "qwq"
ori.state2 = "qaq"
ori.state3 = "quq"
val memento = careTaker.setMemento("001", ori.createMemento())
println(ori)
ori.state1 = "233"
ori.state2 = "hhh"
ori.state3 = "www"
println(ori)
ori.restoreMemento(careTaker.getMemento("001")!!)
println(ori)
}

这里,使用了一个内部类来进行访问,而调用的时候都使用强制转型,这样很好的解决了访问控制问题。我们称这项技术为双接口设计:一个宽接口用来实现正确的业务逻辑,一个窄接口提供给子系统以外的模块访问。

访问者模式

一般的访问者模式

非常简单。有一系列同一继承关系的类,但是他们的获取方法不同。比如老板和员工,获取老板想获取薪水,获取员工想获取补贴,这个时候我们就可以构建一个访问者类,对不同类型进行访问。

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
abstract class Element {
abstract fun work()
abstract fun accept(visitor : IVisitor)
}

class ElementA : Element() {
override fun accept(visitor: IVisitor) {
visitor.visit(this)
}

override fun work() {
println("ElementA")
}
}

class ElementB : Element() {
override fun work() {
println("ElementB")
}

override fun accept(visitor: IVisitor) {
visitor.visit(this)
}
}

interface IVisitor {
fun visit(e1 : ElementA)
fun visit(e2 : ElementB)
}

class Visitor : IVisitor {
override fun visit(e1: ElementA) {
e1.work()
}

override fun visit(e2: ElementB) {
e2.work()
}
}

访问者模式胜在灵活,解决了instanceof这种非常坏的代码,但同时它自身也破坏了依赖倒置原则。当业务结构要求遍历多个不同对象,执行不同操作,这个时候很适合使用访问者模式。

双分派

java是一个单分派语言。这么说很意义不明,如果用人话讲的话,就是java的重载是静态绑定的:

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
open class A
class B : A()
class C : A()

class OutClass {
fun print(a : A) {
println("A")
}
fun print(b : B) {
println("B")
}
fun print(c : C) {
println("C")
}
}

fun main() {
val c = OutClass()
var a : A = A()
c.print(a)
a = B()
c.print(a as A)
a = C()
c.print(a as A)
}

之所以要手动转换,是因为kotlin有smart cast机制,会自动的进行转换。这个时候,我们可以看到输出结果全是A。想要解决这个问题,有没有办法呢?我们有的时候会这样写:

1
2
3
4
5
6
7
fun print(a : A) {
when(a) {
is B -> print(a)
is C -> print(a)
else -> println("A")
}
}

但对于一个OOP语言来说,这种写法往往是大忌!

那么如何实现双分派,也就是根据动态类型和静态类型共同实现重载呢?可以加一层重载的过程。这不禁和访问者模式产生了某种共鸣:

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
open class A {
open fun accept(outClass: OutClass) {
outClass.print(this)
}
}
class B : A() {
override fun accept(outClass: OutClass) {
outClass.print(this)
}
}
class C : A() {
override fun accept(outClass: OutClass) {
outClass.print(this)
}
}

class OutClass {
fun print(a : A) {
println("A")
}
fun print(b : B) {
println("B")
}
fun print(c : C) {
println("C")
}
}

fun main() {
val c = OutClass()
var a : A = A()
a.accept(c)
a = B()
a.accept(c)
a = C()
a.accept(c)
}

把重载放在重写之前,以一种非常巧妙的方式套了一层来实现双分派。

状态模式

弱化版状态机。

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
abstract class State {
var context = Context()
abstract fun work1()
abstract fun work2()
}

class State1 : State() {
override fun work1() {
println("do work1")
}

override fun work2() {
context.currState = Context.STATE2
}
}

class State2 : State() {
override fun work1() {
context.currState = Context.STATE1
}

override fun work2() {
println("do work2")
}
}

class Context {
companion object {
val STATE1 : State = State1()
val STATE2 : State = State2()
}
var currState : State = STATE1
set(value) {
field = value
field.context = this
}
fun work1() {
currState.work1()
}
fun work2() {
currState.work2()
}
}

反正1202年,大家都用状态机了(

解释器模式

这个也不太想看了,例子是个弱化表达式求值。总之就是语法分析。

新模式

规格模式

规格模式使用场景比较固定,主要针对的是对数据集的查询,实现一种类似LINQ的结果。

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
46
47
48
49
50
51
52
53
54
55
56
fun interface ICondition<T> {
fun cond(ele : T) : Boolean
}

abstract class CompositSpecification<T> : ICondition<T> {
fun and(spec : ICondition<T>) : CompositSpecification<T> {
return AndSpecification(this, spec)
}

fun or(spec: ICondition<T>) : CompositSpecification<T> {
return OrSpecification(this, spec)
}

fun not(spec: ICondition<T>) : CompositSpecification<T> {
return NotSpecification(this)
}
}

class AndSpecification<T>(val L : ICondition<T>, val R : ICondition<T>) : CompositSpecification<T>() {
override fun cond(ele : T): Boolean {
return L.cond(ele) && R.cond(ele)
}
}

class OrSpecification<T>(val L : ICondition<T>, val R : ICondition<T>) : CompositSpecification<T>() {
override fun cond(ele : T): Boolean {
return L.cond(ele) || R.cond(ele)
}
}

class NotSpecification<T>(val X : ICondition<T>) : CompositSpecification<T>() {
override fun cond(ele : T): Boolean {
return !X.cond(ele)
}
}

fun <T> judge(con : ICondition<T>) : CompositSpecification<T> {
return object : CompositSpecification<T>() {
override fun cond(ele: T): Boolean {
return con.cond(ele)
}
}
}

fun main() {
val lst = listOf<String>("aaa", "bbb", "ccc")
lst.filter { it ->
judge<String> {
it.length == 3
}.and {
it != "bbb"
}.cond(it)
}.forEach {
print(it)
}
}

试图fp,不太成功,就这样了【

对象池模式

一个简单的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
abstract class ObjectPool<T> {
private var pool = mutableMapOf<T, ObjectStatus>()
init {
pool.put(create(), ObjectStatus())
}

class ObjectStatus {
fun use() {}
fun free() {}
fun validate() : Boolean { return false }
}

@Synchronized fun checkOut() : T? {
for (e in pool.filter { it.value.validate() }) {
e.value.use()
return e.key
}
return null
}

abstract fun create() : T

}

这是一个简单实现,还要考虑很多因素。

雇工模式

这模式大概就是个简化命令模式,我也没看懂有啥用。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface IServiced {
fun work()
}

class Service1 : IServiced {
override fun work() {
println("111")
}
}

class Service2 : IServiced {
override fun work() {
println("222")
}
}

class Servant {
fun work(service : IServiced) {
service.work()
}
}

黑板模式

消息队列。生产者向黑板上发消息,消费者处理。可以给消费者安装一个过滤器。

空对象模式

为了防止null的产生,创建一个空对象,有各种空方法。没什么用。

Author

LittleRewriter

Posted on

2021-09-28

Updated on

2021-12-02

Licensed under

Comments