java solid设计原则_面向对象设计的SOLID原则

文章首发于公众号:松花皮蛋的黑板报

作者就职于京东,在稳定性保障、敏捷开发、高级JAVA、微服务架构有深入的理解

ebda1508d723b3ed287fb9ebdb0d34d8.png

1、单一职责原则

考虑下面这个类

class Animal {

constructor(name: string){ }

getAnimalName() { }

saveAnimal(a: Animal) { }

}

它实际上违背了单一职责原则SRP。上面的类其实有两个职责,一为动物实体的持久化管理,另外一个为动物的属性管理。那我们应该如何设计避免这种错误呢?我们可以新建另外一个类,它负责将实体对象存储到数据库上。如下所示:

class Animal {

constructor(name: string){ }

getAnimalName() { }

}

class AnimalDB {

getAnimal(a: Animal) { }

saveAnimal(a: Animal) { }

}

当我们设计类时需要注意的一点是,如果功能因不同原因而发生变化,我们应该尝试将功能分开。

2、开闭原则

这个原则强调现有接口规范可以通过继承重用,不要修改现有已完成的接口。我们继续以动物这个类说明

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);

上面的示例违背了开闭原则,当有新的类型需要处理时,将不得不在原有代码上进行修改,导致大量的阅读性差的IF条件语句。那我们应该怎么设计避免这种错误呢?我们可以定义一个有makeSound方法的共同类比如说Animal类,然后每个具体动物类继承并重写makeSound方法,完成个性化处理。另外真正的处理业务的AnimalSound类遍历Animal列表然后调用makeSound方法即可。当新扩展一个具体动物类时,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);

3、里氏替换原则

这个原则强调当一个类继承另外一个类时,除添加新的方法外,尽量不要重写或者重载父类的方法。否则引用基类完成的功能,换成子类后就会出现异常。我们继续使用Animal类进行说明,先看一段代码,如下://...

function AnimalLegCount(a: Array) {

for(int i = 0; i <= a.length; i++) {

if(typeof a[i] == Lion)

log(LionLegCount(a[i]));

if(typeof a[i] == Mouse)

log(MouseLegCount(a[i]));

if(typeof a[i] == Snake)

log(SnakeLegCount(a[i]));

}

}

AnimalLegCount(animals);

上面的示例违背了这个原则,因为必须知道每个类型才能决定使用哪个方法。那我们应该怎么设计才能避免这种错误呢?我们应该保证子类的方法参数必须和超类的参数类型等同,或者为其超类参数的子类;我们应该保证子类的方法返回类型必须和超类的返回类型等同,或者为其超类返回类型的子类。接下来我们进行代码改造:

function AnimalLegCount(a: Array) {

for(let i = 0; i <= a.length; i++) {

a[i].LegCount();

}

}

AnimalLegCount(animals);

改造后的方法不对参数进行任何类型判断,只关心它是否为Animal类或者为Animal的子类然后调用LegCount方法。

4、接口隔离原则

这个原则强调不应该强迫实现类实现它不需要的接口方法。假设有如下这个接口类:

interface IShape {

drawCircle();

drawSquare();

drawRectangle();

}

它有三个互不相关的方法,然而子类必须实现永远使用不上的方法。当我们在接口类中新增一个抽象方法时,必须修改所有子类然后实现可能更离谱的方法。那我们应该怎么设计避免这种错误呢?答案是接口隔离,如下:

interface IShape {

draw();

}

interface ICircle {

drawCircle();

}

interface ISquare {

drawSquare();

}

interface IRectangle {

drawRectangle();

}

interface ITriangle {

drawTriangle();

}

class Circle implements ICircle {

drawCircle() {

//...

}

}

class Square implements ISquare {

drawSquare() {

//...

}

}

class Rectangle implements IRectangle {

drawRectangle() {

//...

}

}

class Triangle implements ITriangle {

drawTriangle() {

//...

}

}

class CustomShape implements IShape {

draw(){

//...

}

}

5、依赖倒置原则

这个原则强调高层模块不应该依赖具体细节实现模块,两者都应该依赖上层的抽象模块。当然抽象不应该依赖细节,细节才应该依赖上层的抽象。我们以下面的代码进行说明:class XMLHttpService extends XMLHttpRequestService {}

class Http {

constructor(private xmlhttpService: XMLHttpService) { }

get(url: string , options: any) {

this.xmlhttpService.request(url,'GET');

}

post() {

this.xmlhttpService.request(url,'POST');

}

//...

}

复制代码上面的示例违背了这个原则,高层模块的Http类依赖了低层模块的XMLHttpService类,当我们改变请求的实现时,比如使用NodeJs、Mock服务,我们需要细心地重构上述代码,得不偿失。实际上高层模块的Http类不应该关心具体的的实现细节,接起来,我们进行改造。定义一个请求抽象类Connection类

interface Connection {

request(url: string, opts:any);

}

Http类的参数修改为抽象Connection类

class Http {

constructor(private httpConnection: Connection) { }

get(url: string , options: any) {

this.httpConnection.request(url,'GET');

}

post() {

this.httpConnection.request(url,'POST');

}

//...

}

复制代码这样的好处是,Http类可以轻松完成请求业务逻辑而无须关注具体的实现类型。

文章来源:www.liangsonghua.me

作者介绍:京东资深工程师-梁松华,在稳定性保障、敏捷开发、JAVA高级、微服务架构方面有深入的理解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值