开发者都应该了解的SOLID原则(上)

2019年06月20日 由 sunlei 发表 789458 0
面向对象的编程类型为软件开发带来了新的设计。

这使开发人员能够在一个类中组合具有相同目的/功能的数据,来实现单独的一个功能,不必关心整个应用程序如何。

但是,这种面向对象的编程还是会让开发者困惑或者写出来的程序可维护性不好。

为此,Robert C.Martin指定了五项指导方针。遵循这五项指导方针能让开发人员轻松写出可读性和可维护性高的程序

这五个原则被称为S.O.L.I.D原则(首字母缩写词由Michael Feathers派生)。

  • S:单一责任原则

  • O:开闭原则

  • L:里式替换

  • I:接口隔离

  • D:依赖反转


我们在下文会详细讨论它们。

笔记:本文的大多数例子可能不适合实际应用或不满足实际需求。这一切都取决于您自己的设计和用例。这都不重要,关键是您要了解明白这五项原则。

提示:SOLID原则旨在用于构建模块化、封装、可扩展和可组合组件的软件。Bit是一个帮助你践行这些原则的强大工具:它可以帮助您在团队中大规模地在不同项目中轻松隔离,共享和管理这些组件.来试试吧。

单一功能原则 Single Responsibilty Principle


一个类应该只负责一件事。如果一个类负责超过一件事,就会变得耦合。改功能的时候会影响另外一个功能。

  • 笔记:该原则不仅适用于类,还适用于软件组件和微服务。


举个例子,考虑这个设计:
class Animal {
constructor(name: string){ }
getAnimalName() { }
saveAnimal(a: Animal) { }
}

这个Animal类违反了SRP(单一责任原则)

怎么违反了呢?

SRP明确说明了类只能完成一项功能,这里,我们把两个功能都加上去了:animal数据管理和animal属性管理。构造函数和getAnimalName方法管理Animal的属性,然而,saveAnimal方法管理Animal的数据存储。

这种设计会给以后的开发维护带来什么问题?

如果app的更改会影响数据库的操作。必须会触及并重新编译使用Animal属性的类以使app的更改生效。

你会发现这样的系统缺乏弹性,像多米诺骨牌一样,更改一处会影响其他所有的地方。

让我们遵循SRP原则,我们创建了另外一个用于数据操作的类:
class Animal {
constructor(name: string){ }
getAnimalName() { }
}
class AnimalDB {
getAnimal(a: Animal) { }
saveAnimal(a: Animal) { }
}

“我们在设计类时,我们应该把相关的功能放在一起,所以当他们需要发生改变时,他们会因为同样的原因而改变。如果是因为不同的原因需要改变它们,我们应该尝试把它们分开。” - Steven Fenton

遵循这些原则让我们的app变得高内聚。

开闭原则 Open-Closed Principle


软件实体(类,模块,函数)应该是可以扩展的,而不是修改。

继续看我们的Animal类
class Animal {
constructor(name: string){ }
getAnimalName() { }
}

我们想要遍历动物列表并且设置它们的声音。
//...
const animals: Array = [
new Animal('lion'),
new Animal('mouse')
];
function AnimalSound(a: Array) {
for(int i = 0; i <= a.length; i++) {
if(a[i].name == 'lion')
log('roar');
if(a[i].name == 'mouse')
log('squeak');
}
}
AnimalSound(animals);

AnimalSound函数并不符合开闭原则,因为一旦有新动物出现,它需要修改代码。

如果我们加一条蛇进去:
//...
const animals: Array = [
new Animal('lion'),
new Animal('mouse'),
new Animal('snake')
]
//...

我们不得不改变AnimalSound函数:
//...
function AnimalSound(a: Array) {
for(int i = 0; i <= a.length; i++) {
if(a[i].name == 'lion')
log('roar');
if(a[i].name == 'mouse')
log('squeak');
if(a[i].name == 'snake')
log('hiss');
}
}
AnimalSound(animals);

每当新的动物加入,AnimalSound函数就需要加新的逻辑。这是个很简单的例子。当你的app变得庞大和复杂时,你会发现每次加新动物的时候就会加一条if语句,随后你的app和AnimalSound函数都是if语句的身影。

那怎么修改AnimalSound函数呢?
class Animal {
makeSound();
//...
}
class Lion extends Animal {
makeSound() {
return 'roar';
}
}
class Squirrel extends Animal {
makeSound() {
return 'squeak';
}
}
class Snake extends Animal {
makeSound() {
return 'hiss';
}
}
//...
function AnimalSound(a: Array) {
for(int i = 0; i <= a.length; i++) {
log(a[i].makeSound());
}
}
AnimalSound(animals);

现在Animal有个makeSound的私有方法。我们每一个animal继承了Animal类并且实现了私有方法makeSound。

每个animal实例都会在makeSound中添加自己的实现方式。AnimalSound方法遍历animal数组并调用其makeSound方法。

现在,如果我们添加了新动物,AnimalSound方法不需要改变。我们需要做的就是添加新动物到动物数组。

AnimalSound方法现在遵循了开闭原则。

另一个例子:

假设您有一个商店,并且您使用此类给您喜爱的客户打2折:
class Discount {
giveDiscount() {
return this.price * 0.2
}
}

当您决定为VIP客户提供的折扣翻倍。 您可以像这样修改类:
class Discount {
giveDiscount() {
if(this.customer == 'fav') {
return this.price * 0.2;
}
if(this.customer == 'vip') {
return this.price * 0.4;
}
}
}

哈哈哈,这样不就背离开闭原则了么?如果我们又想加新的折扣,那又是一堆if语句。

为了遵循开闭原则,我们创建了继承Discount的新类。在这个新类中,我们将会实现新的行为:
class VIPDiscount: Discount {
getDiscount() {
return super.getDiscount() * 2;
}
}
如果你决定给VIP80%的折扣,就像这样:

class SuperVIPDiscount: VIPDiscount {
getDiscount() {
return super.getDiscount() * 2;
}
}

你看,这不就不用改了。

今天get到新的收获了吗?还有一部分内容,我接下来会更新,期待吧。

 
欢迎关注ATYUN官方公众号
商务合作及内容投稿请联系邮箱:bd@atyun.com
评论 登录
写评论取消
回复取消