文章首发于公众号:松花皮蛋的黑板报数据库
做者就任于京东,在稳定性保障、敏捷开发、高级JAVA、微服务架构有深刻的理解bash
考虑下面这个类架构
class Animal {
constructor(name: string){ }
getAnimalName() { }
saveAnimal(a: Animal) { }
}
复制代码
它实际上违背了单一职责原则SRP。上面的类其实有两个职责,一为动物实体的持久化管理,另一个为动物的属性管理。微服务
那咱们应该如何设计避免这种错误呢?咱们能够新建另一个类,它负责将实体对象存储到数据库上。以下所示:post
class Animal {
constructor(name: string){ }
getAnimalName() { }
}
class AnimalDB {
getAnimal(a: Animal) { }
saveAnimal(a: Animal) { }
}
复制代码
当咱们设计类时须要注意的一点是,若是功能因不一样缘由而发生变化,咱们应该尝试将功能分开。ui
这个原则强调现有接口规范能够经过继承重用,不要修改现有已完成的接口。this
咱们继续以动物这个类说明url
class Animal {
constructor(name: string){ }
getAnimalName() { }
}
复制代码
咱们的需求是让列表中每一个动物发出不一样的声音,以下所示spa
//...
const animals: Array<Animal> = [
new Animal('lion'),
new Animal('mouse')
];
function AnimalSound(a: Array<Animal>) {
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<Animal>) {
for(int i = 0; i <= a.length; i++) {
log(a[i].makeSound());
}
}
AnimalSound(animals);
复制代码
这个原则强调当一个类继承另一个类时,除添加新的方法外,尽可能不要重写或者重载父类的方法。不然引用基类完成的功能,换成子类后就会出现异常。
咱们继续使用Animal类进行说明,先看一段代码,以下:
//...
function AnimalLegCount(a: Array<Animal>) {
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<Animal>) {
for(let i = 0; i <= a.length; i++) {
a[i].LegCount();
}
}
AnimalLegCount(animals);
复制代码
改造后的方法不对参数进行任何类型判断,只关心它是否为Animal类或者为Animal的子类而后调用LegCount方法。
这个原则强调不该该强迫实现类实现它不须要的接口方法。
假设有以下这个接口类:
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(){
//...
}
}
复制代码
这个原则强调高层模块不该该依赖具体细节实现模块,二者都应该依赖上层的抽象模块。固然抽象不该该依赖细节,细节才应该依赖上层的抽象。
咱们如下面的代码进行说明:
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高级、微服务架构方面有深刻的理解