dream

一个菜鸟程序员的成长历程

0%

设计模式之原型模式–打印机快速复制的原理

原型模式是一个克隆模式,以一个原型进行克隆,复制。

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

为什么要使用原型模式

因为原型模式可以克隆整个对象而不用重新生成。

如果有一个订单对象,你要是重新生成,需要再次查询数据库,这是一个很耗时的操作,如果直接复制就不需要耗时了。

在php里面实现很简单,每个类有__clone魔术方法,实现这个方法就好了。

先看不实现这个方法的克隆。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 原型模式
* 实现__clone魔术方法
*/
class Prototype {

public $name = '张三';
public $arr = ['1', '2'];

}

$obj1 = new PrototypeClass;
dump($obj1);

$obj2 = clone $obj1;
$obj2->name = '456';
dump($obj2);

原型模式

可以看到成功克隆过来了,两个对象互不影响。

我们看一下修改第二个对象的arr属性呢

1
2
3
4
5
6
7
8
9

$obj1 = new PrototypeClass;
dump($obj1);

$obj2 = clone $obj1;
$obj2->name = '456';
$obj2->arr = ['1'];
dump($obj2);

原型模式

修改数组也没问题,也就是说,直接使用clone就可以完成深复制的拷贝操作。php本身通过clone关键字完成了原型模式。

代码放在了我的github上面。

设计模式之建造者模式–比工厂更精细的流水线生成

建造者模式是创建一个稳定流程的复杂对象,隐藏创建的具体流程、过程、细节。

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

为什么要使用建造者模式

当你需要创建一些复杂的对象,这些对象内部构建间的建造顺序通常是稳定的,但对象内部的构建通常面临着复杂的变化

建造者分离了构建和表示,他把如何创建隐藏了起来,你只要告诉建造者你需要什么,他就会给你建造什么。

建造者模式和工厂模式有什么区别

工厂模式更像一个大工厂,比如口罩工厂负责生产雾霾口罩防毒口罩等等。根据你需要的类型来判断给你什么

雾霾口罩怎么生产呢?假设需要123三个步骤,那么建造者就是负责这三个步骤的,你告诉建造者你需要雾霾口罩,那么他会给你一个完整的,建造好的雾霾口罩而不是口罩半成品

所以建造者更像一个完整的流水线,按照步骤建造好成品给你。

再比如你去饭店点餐,工厂模式是你点哪个菜就给你哪个菜。建造者模式是你点的菜怎么做。你点了鱼香肉丝,建造者会按照步骤,哪一步该放肉,哪一步该放盐,最后做好了把菜给你。

建造者模式还有一个作用就是约束。抽象出的建造流程不可改变,口罩不能少个过滤网,饭菜不能没有放盐。

建造者模式也避免了复杂对象创建流程的缺失。

php实现建造者模式

假设现在有一个对接第三方支付的需求。使用建造者模式。

我们要先抽象出一个建造流程。使用抽象类,对接第三方支付大概需要一个加密,一个读取配置信息,然后把这些参数合并发送出去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

<?php
/**
* 建造者模式
* 建造者抽象类
* 抽象出整个建造流程
*/
abstract class Build {

/**
* 加密流程
*/
public abstract function buildHash();

/**
* 读取配置流程,比如appid这些
*/
public abstract function buildConfig();

}

接下来我们的第三方支付类继承这个抽象建造者来实现具体的第三方支付建造者。

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
/**
* 建造者模式
* 建造者抽象类
* 抽象出整个建造流程
*/
class ThreadBuild extends Build{

/**
* 加密流程
*/
public function buildHash() {
//省略加密代码
return 'hash';
}

/**
* 读取配置流程,比如appid这些
*/
public function buildConfig() {
//省略配置代码
return 'config';
}

/**
* 发送http请求流程
*/
public function http() {
//省略发送代码
return 'success';
}
}

有了建造者,我们现在需要一个组装这整个建造流程的监工指挥者。来保证整个流程不出错。

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 建造者模式
* 指挥者类
* 按照流程建造,返回建造后的类。
*/
class Director{
static function createBuild(Build $build) {
$build->buildHash();
$build->buildConfig();
return $build;
}
}

我们的客户端调用。

1
2
3
4

$threadPay = Director::createBuild(new ThreadBuild());
$threadPay->http();

如果接入了别的第三方支付,那么我们只需要增加具体建造者就可以了,如果和抽象建造者冲突,那么说明没有抽象好。在客户端只需要更改具体建造者就可以了。

代码放在了我的github上面。

设计模式之工厂方法模式–更加符合开闭原则的工厂模式

工厂方法模式是简单工厂模式的升级版本,更加符合开闭原则。

定义一个创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。

为什么要使用工厂方法模式

之前说了简单工厂模式简单工厂模式的工厂类是这样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

//简单工厂类
class factory{
function static createPay() {
if (小程序) {
//小程序支付
$pay = new miniWechatPay;
} else if (公众号) {
//公众号支付
$pay = new jsAPIPay;
} else if (pc) {
//扫码支付
$pay = new nativePay;
}
return $pay;
}
}

这样的工厂类是违反了开闭原则的,如果需要增加支付方法就需要修改这个工厂类,工厂方法模式就是解决这个问题的,使用工厂方法模式后不需要修改工厂类,只需要新增工厂类

改成工厂方法模式

为了符合开闭原则,我们需要创建多个工厂类和一个工厂接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

interface IFactory{
static function createPay();
}

class miniFactory implements IFactory{
static function createPay() {
return new miniWechatPay;
}
}

class jsAPIFactory implements IFactory{
static function createPay() {
return new jsAPIPay;
}
}

class nativeFactory implements IFactory{
static function createPay() {
return new nativePay;
}
}

好了,我们把实例化放到了这些子类中,如果增加实例化的需求只需要增加工厂类就可以了。这就是工厂方法模式。

相比简单工厂来说,这有一个缺点就是需要修改的时候虽然不需要修改工厂类了,但是需要修改客户端了。

下面的是原来的客户端。

1
2
3
4
5
6
7

//微信支付方法 省略了类
function wechatPay() {
//...省略
$pay = factory::createPay();
$pay->pay();
}

下面的是工厂方法模式的客户端

1
2
3
4
5
6
7
8

//微信支付方法 省略了类
function wechatPay() {
//...省略
$pay = miniFactory::createPay(); //需要修改这里
$pay->pay();
}

如果不想修改客户端也可以再增加一个简单工厂类来实例化工厂方法模式的类。

1
2
3
4
5
6
7
8
9
class factory{
static function createFactory() {
if (小程序) {
return miniFactory::createPay();
}
//省略其他判断和实例化
}
}

但是我觉得这样还不如直接用简单工厂来的好。

总的来说,简单工厂和工厂方法模式各有优缺点吧,不过对于php来说我觉得简单工厂就足以,虽然有修改代码的风险。但是也还好,而且通过封装成数组来实例化也不需要这一堆if-else。

代码放在了我的github上面。

解决composer-npm等包管理工具下载失败的问题

使用composer npm进行下载依赖包的时候经常需要翻墙。不过国内已经有了很多国内源来加速下载,比如阿里云的国内镜像等。

不过有时候国内镜像并不能解决问题。

还有的时候就算你挂了全局翻墙也不行。

这是因为你使用命令行的时候,是用不到翻墙工具的。

如果你有翻墙工具,那么可以在命令行上面设置一下,使用代理就可以了。

在gitbash上面的设置。

export http_proxy=http://127.0.0.1:41091 //这个端口是你本地代理工具提供的端口
export https_proxy=http://127.0.0.1:41091 //这个端口是你本地代理工具提供的端口

在cmd上的设置

set http_proxy=http://127.0.0.1:41091 //这个端口是你本地代理工具提供的端口
set https_proxy=http://127.0.0.1:41091 //这个端口是你本地代理工具提供的端口

设计模式之简单工厂模式–利用工厂解耦实例化对象

简单工厂模式是最常被提起的一个设计模式,他的意思是

利用简单工厂来决定实例化哪个类,而不是由外部程序来决定,把创建对象的操作内聚,解耦到工厂类中。

为什么要使用简单工厂模式

工厂模式除了简单工厂模式还有工厂方法模式,抽象工厂模式

简单工厂模式有什么好处呢?

简单,非常简单。并且拥有工厂模式的特性,解耦对象的生成。

如果不使用工厂模式,那么对象的创建散落在程序的各个地方,如果需要修改,那么很麻烦。

我们在设计类的时候,为了遵循单一职责原则,我们应该把类划分的尽可能单一,拿一个实际应用例子来说,支付场景。

支付场景

我们需要在小程序端使用小程序支付,公众号端使用h5支付,pc端使用扫码支付。

后端统一提供支付接口。代码类似下面这样。

1
2
3
4
5
6
7
8
9
10
function wechatPay() {
//...省略
if (小程序) {
//小程序支付
} else if (公众号) {
//公众号支付
} else if (pc) {
//扫码支付
}
}

上面的是一个伪代码,这样写不管扩展性,维护性都不好,更不符合单一职责,所以我们应该把支付逻辑抽出来。

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
interface pay{
function pay();
}

class miniWechatPay implements pay{
function pay() {
//调用小程序支付
}
}

class jsAPIPay implements pay{
function pay() {
//调用公众号,jsapi支付
}
}

class nativePay implements pay{
function pay() {
//调用扫码支付
}
}

//微信支付方法 省略了类
function wechatPay() {
//...省略
if (小程序) {
//小程序支付
$pay = new miniWechatPay;
$pay->pay();
} else if (公众号) {
//公众号支付
$pay = new jsAPIPay;
$pay->pay();
} else if (pc) {
//扫码支付
$pay = new nativePay;
$pay->pay();
}
}

在if判断里面有重复的地方,都是调用pay方法,我们可以抽出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//微信支付方法 省略了类
function wechatPay() {
//...省略
if (小程序) {
//小程序支付
$pay = new miniWechatPay;
} else if (公众号) {
//公众号支付
$pay = new jsAPIPay;
} else if (pc) {
//扫码支付
$pay = new nativePay;
}

$pay->pay();
}

如果接下来我们还要增加刷脸支付等等其他支付怎么办呢,我们只能修改这个方法,这显然是不对的,违反了开闭原则

增加支付其实和这个业务逻辑不是一个紧耦合的,我们应该增加他的复用性,如果我们要在其他地方支付呢。应该把实例化这部分也抽出来。就形成了简单工厂类

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

//简单工厂类
class factory{
function static createPay() {
if (小程序) {
//小程序支付
$pay = new miniWechatPay;
} else if (公众号) {
//公众号支付
$pay = new jsAPIPay;
} else if (pc) {
//扫码支付
$pay = new nativePay;
}
return $pay;
}
}


//微信支付方法 省略了类
function wechatPay() {
//...省略
$pay = factory::createPay();
$pay->pay();
}


这样拆分有什么好处呢?

虽然增加支付的时候依旧需要修改代码,但是只需要修改简单工厂类,而不用修改业务逻辑类,避免了因为修改业务逻辑类而产生的业务逻辑bug。使得实例化对象和具体业务无关。

增加了代码的复用性,维护性,灵活性,测试性等。

代码放在了我的github上面。

设计模式之单例模式–php实现单例模式

单例模式是最常被提起的一个设计模式,他的意思是

一个类只有一个实例,所有人共用这同一个实例。不让外部创建类的实例,并且提供一个外部的全局访问点。

为什么要使用单例模式

单例模式有什么好处呢?

单例模式是为了全局唯一,如果你需要一个全局唯一的类那么就需要单例模式了。而且单例模式还可以节省资源,因为这个类只有一个对象。

比如你需要一个全局唯一的id生成器,如果创建了多个实例,那么生成的id可能就不是全局唯一了。

比如你需要一个全局配置信息,需要全局缓存信息,全局日志文件写入等等。

普通类

下面是一个普通的类的实现和使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Common {
public $name; //姓名
public $age; //年龄

function __construct($name, $age)
{
$this->name = $name;
$this->age = $age;
}

public function print() {
dump('姓名:'.$this->name);
dump('年龄:'.$this->age);
}
}

$obj1 = new Common('张三', '18');
dump($obj1);

$obj2 = new Common('张三', '20');
dump($obj1);

可以看到普通类可以new多个对象,并且每个对象都不一样,拥有自己的属性。

单例类

首先,单例类是一种特殊的类,单例类只允许new一次。怎么实现只能new一次呢,从上面的代码中可以看到,new这个操作是在我们的业务中进行的,这是个无法掌控的操作。

我们要让new操作在类中实现,由这个单例类来决定,来操作对象的实例化。而不是在业务中进行。

只有自己才能决定自己newnew,自己决定自己的人生,而不是交给别人,使得无法掌控。

从这个分析中可以知道,单例类要完成下面的操作:

  • 自己创建实例化,供外部全局访问
  • 不允许外部实例化

第一点简单,实现一个方法new自己,然后存起来返回就可以了。

第二点在php中怎么实现呢,不允许外部实例化可以通过将__construct方法设置为private权限。因为这个方法外部访问不到,所以外部就无法实例化了。

简单实现一下单例类。

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
class Singleton {
public $name; //姓名
public $age; //年龄
private static $instance;

private function __construct($name, $age)
{
$this->name = $name;
$this->age = $age;
}

public static function getInstance($name, $age) {
if (empty(self::$instance)) {
self::$instance = new self($name, $age);
}
return self::$instance;
}

public function print() {
dump('姓名:'.$this->name);
dump('年龄:'.$this->age);
}
}

$obj1 = Singleton::getInstance('张三', '18');
dump($obj1);
$obj2 = Singleton::getInstance('张三', '20');
dump($obj2);

这是把上面的普通类改成了单例类,现在这个单例类还有些问题。

  • 参数上面第二个参数没有生效

单例类一般不会出现这种需要各种参数的,需要参数也建议通过配置文件来搞定参数,而不是传参。如果有传参需求,需要的是不同的参数是不同的对象,同一个参数是单例对象,那么需要维护一个单例对象的数组。为什么不提供修改参数的方法呢,因为提供这个方法太危险,全局使用同一个对象,如果某个地方不慎更改了属性,那么会导致其他地方出现未知bug。

  • 并没有完全禁止外部实例化,php还有魔术方法__clone。

把__clone方法设置成private

根据上面的我们在修改一下这个单例类。

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

class Singleton {
protected $name; //姓名
protected $age; //年龄

//用来存放全局唯一对象
private static $instance;

/**
* 私有化构造函数使得外部无法new对象
* 构造函数去掉参数,参数从配置文件读取
*/
private function __construct()
{
$this->name = config('design.singleton.name');
$this->age = config('design.singleton.age');
}

/**
* 私有化克隆函数使得外部无法克隆
*/
private function __clone() {

}

/**
* 获取全局唯一对象的时候不需要传参
*/
public static function getInstance() {
if (empty(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}

public function print() {
dump('姓名:'.$this->name);
dump('年龄:'.$this->age);
}
}

$obj1 = Singleton::getInstance();
dump($obj1);
$obj2 = Singleton::getInstance();
dump($obj2);

到现在,我们就完成了一个基于php的单例类,这个单例类支持延迟加载。如果不需要延迟加载,那么实现更简单。看下面的饿汉式单例类

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

class Singleton {
protected $name; //姓名
protected $age; //年龄

//用来存放全局唯一对象 饿汉式单例直接在这里new
private static $instance = new self();

/**
* 私有化构造函数使得外部无法new对象
* 构造函数去掉参数,参数从配置文件读取
*/
private function __construct()
{
$this->name = config('design.singleton.name');
$this->age = config('design.singleton.age');
}

/**
* 私有化克隆函数使得外部无法克隆
*/
private function __clone() {

}

/**
* 获取全局唯一对象的时候不需要传参
* 这里只需要直接返回
*/
public static function getInstance() {
return self::$instance;
}

public function print() {
dump('姓名:'.$this->name);
dump('年龄:'.$this->age);
}
}

我们支持延迟加载的其实还有些问题,那就是缺少锁,如果很多请求同时进来,那么后面的会覆盖前面的单例对象。如果再加上锁的话实现是下面这样。

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

class Singleton {
protected $name; //姓名
protected $age; //年龄

//用来存放全局唯一对象
private static $instance;

/**
* 私有化构造函数使得外部无法new对象
* 构造函数去掉参数,参数从配置文件读取
*/
private function __construct()
{
$this->name = config('design.singleton.name');
$this->age = config('design.singleton.age');
}

/**
* 私有化克隆函数使得外部无法克隆
*/
private function __clone() {

}

/**
* 获取全局唯一对象的时候不需要传参
*/
public static function getInstance() {
//先加锁
$lock = new lock;
$lock->lock();
if (empty(self::$instance)) {
self::$instance = new self();
}
//释放锁
$lock->unlock();
return self::$instance;
}

public function print() {
dump('姓名:'.$this->name);
dump('年龄:'.$this->age);
}
}

这样的话因为锁的原因会造成性能比较低,所以又有了下面的方式,叫做双重检测。也就是拿锁之前先判断一下有没有实例化,如果有了就不需要锁了。为什么不直接在第一个判断里面加呢,因为锁加晚了没有意义了。

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

class Singleton {
protected $name; //姓名
protected $age; //年龄

//用来存放全局唯一对象
private static $instance;

/**
* 私有化构造函数使得外部无法new对象
* 构造函数去掉参数,参数从配置文件读取
*/
private function __construct()
{
$this->name = config('design.singleton.name');
$this->age = config('design.singleton.age');
}

/**
* 私有化克隆函数使得外部无法克隆
*/
private function __clone() {

}

/**
* 获取全局唯一对象的时候不需要传参
*/
public static function getInstance() {
//先判断
if (empty(self::$instance)) {
//先加锁
$lock = new lock;
$lock->lock();
if (empty(self::$instance)) {
self::$instance = new self();
}
//释放锁
$lock->unlock();
}

return self::$instance;
}

public function print() {
dump('姓名:'.$this->name);
dump('年龄:'.$this->age);
}
}

这就是整个单例模式。四种实现:

  • 饿汉式 在类加载的时候实例化
  • 懒汉式 支持延迟加载
  • 带锁懒汉式 延迟加载的时候加锁
  • 双重检测 两次判断

代码放在了我的github上面。

设计原则之KISS原则和YAGNI原则

KISS原则

KISS(Keep It simple and Stupid)原则总的来说就是简单,你的代码要写的简单易懂。增加代码的可读性。

并不一定是代码量的多少来判断简单,而是通过可读性,如果这个代码可读性很好,比如你一下子就能看懂,这就说明符合KISS原则。

这个原则也比较主观,因为如果看代码的人水平比较差可能看不懂,而比你水平好的则可能一下子看懂。就像我们读框架源码读不懂并不是框架源码写的不好而是我们水平不够,哈哈哈哈。

怎么让代码简洁易懂呢?

  • 命名清晰易懂
  • 可以写一些注释辅助看懂
  • 遵守代码规范
  • 统一团队风格

我们要写出可读性好的代码而不是一些复杂的代码。我们的目的是写出实现需求的代码。

YAGNI原则

YAGNI(You Ain't Gonna Need It)原则的意思是你不需要他的时候就不要提前写好,不要做过度设计。我们可以基于扩展性留好’坑’,方便以后扩展新的代码。比如我们现在使用了微信支付,以后可能会接入支付宝支付,我们可以基于接口编程方便扩展,但不用直接写好支付宝支付的代码。

同样的,我们不要在代码中依赖不需要的东西,比如包管理,我们只需要当前需要的扩展包,不要把现在不需要的都加入进来。

比如vue的组件化设计,引入一些组件的时候,比如element ui组件,可以选择全部引入也可以选择按需加载。

还有各种懒加载,比如图片懒加载,树形结构的懒加载,我们只加载当前需要的东西。

参考资料:

  • 极客时间设计模式之美

设计原则之迪米特法则–我只依赖我需要的类

迪米特法则(Law of Demeter) LOD。这个原则是说我只依赖我确实需要的类,也叫最小知识原则。

这个原则的英文Each unit should have only limited knowledge about other units:only units ‘closely’ related to the current unit. Or:Each unit should only talk to its friends; Don’t talk to strangers.

每个模块之应该了解哪些与他关系密切的模块的有限知识。或者说,每个模块只和自己的朋友说话,而不和陌生人说话。

如果说用好了单一职责可以写出高内聚的代码,那么用好了迪米特就可以实现低耦合。

在类的结构上,每一个类都应当尽量降低成员的访问权限。这样可以避免别人调用不应该调用的方法。我们只对外暴露应该暴露的方法。

这么做有什么好处呢?如果你的依赖越少,或者依赖你的类越少,那么当你修改的时候,你影响到的也越少,出bug的概率也越少。

我们应该设计好类之间的依赖关系。谁也不想管理一团乱麻的代码。如果你现在的项目依赖关系混乱,那么你可能根本不敢修改任何一个地方,生怕整个系统崩溃。依赖关系过多也会导致不好测试。

设计原则之依赖倒置原则–我的依赖被反转了

依赖倒置原则(Dependency Inversion Principle)DIP。这个原则的英文是high-level modules shouldn't depend on low-level modules. both modules should depend on abstractions. In addition, abstractions shouldn't depend on details.Details depend on abstractions.。意思是高层模块不要依赖底层模块。高层模块和底层模块都应该依赖抽象。抽象不要依赖具体实现,具体实现应该依赖抽象。

什么是依赖倒置原则

通常来说,调用者属于高层模块,被调用者就是低层模块。为什么叫依赖倒置或者依赖反转呢?正常开发来说类A调用类B,类A属于高层模块,类B是低层模块。高层模块依赖低层模块,需要调用低层模块的方法。直接和低层模块高度耦合。

如果这时候我们的低层模块需要适配不同的高层模块,那么就无法复用。因为低层模块和以前的高层模块耦合在一起,如果修改适配新模块可能会导致以前的高层模块出现问题。

依赖倒置原则就是把这个依赖关系进行反转。以前是高层模块依赖低层模块,现在我不依赖你了。咋俩都依赖抽象。我们使用抽象类或者接口。我们的实现都基于这个接口来进行,而不是产生直接的依赖关系。我们都依赖同样的接口,同样的抽象。

比如我们的电脑有CPU,键盘,鼠标,内存,硬盘,显示器这些东西,我们只要组装起来就是电脑,这些东西就像高内聚的程序。内聚在一起,他们依赖相同的接口进行调用,你的罗技鼠标雷蛇鼠标都使用同一个接口。那么电脑就可以正常运转。

如何使用依赖倒置原则

我们进行软件设计的时候应该由上而下的设计,先进行抽象设计,然后来具体实现。如果先写出具体实现在进行抽象设计,那么抽象出来的东西就容易依赖具体实现。因为具体实现很可能会变,但是抽象一般不会改变。所以抽象使得程序更加稳定。

那么为什么抽象一般不会改变呢?这就是之前说的里氏替换原则。子类可以替换父类,需要改变我们只需要扩展具体实现就可以而不是修改抽象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

//电脑需要一个能插入usb接口的鼠标
class computer{
public function __construct(USB $mouse) {
$this->mouse = $mouse;
}
}

interface USB{
public function usb();
}

//雷蛇鼠标实现了usb接口
class snakeMouse implements USB{
public function usb() {}
}

//罗技鼠标实现了usb接口
class luoMouse implements USB{
public function usb() {}
}

在这里,不管是哪个鼠标都可以使用,如果我们依赖具体的鼠标,那么就无法灵活更换了。两边,调用者和被调用者,高层模块和低层模块都依赖抽象。

参考资料:

  • 大话设计模式
  • 极客时间设计模式之美

设计原则之接口隔离原则–如何通过接口隔离职责

接口隔离原则(Interface Segregation Principle) ISP。这个原则是说客户端不应该依赖他不需要的接口。

这个原则的英文是Clients should not be forced to depend upon interfaces that they do not use

如何使用接口隔离原则

这里面的接口不同于我们的API接口,也不是电脑的USB接口这种,而是我们程序中使用的接口Interface

我们通过让程序实现不同的接口来完成不同的职责。这个原则和单一职责原则也有点类似。比如一个类既有查询功能还有修改功能。

1
2
3
4
5
6
7
8
9
class demo{
public function list() {

}

public function update(){

}
}

那么现在有一个类需要使用这个类的查询功能。它只需要使用查询,但是他还是可以知道这个类有修改功能,可以使用他的修改功能。另外有一个类只需要修改却同样被迫加载了查询功能。

如果我们增加两个接口。

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 list {
public function list()
}

interface update{
public function update()
}

class demo implements list, update{
public function list() {

}

public function update(){

}
}

class testList {
public function test(list $demo) {
$demo->list();
}
}

class testUpdate {
public function test(update $demo) {
$demo->update();
}
}

这样的话,我们通过接口将查询和更新分离开,查询方只依赖查询接口,只能感知到查询操作,更新方只依赖更新接口,只能感知到更新操作,不需要知道这个类其他的功能,也防止了误操作。

参考资料:

  • 大话设计模式
  • 极客时间设计模式之美