文章首發於公衆號:松花皮蛋的黑板報數據庫
做者就任於京東,在穩定性保障、敏捷開發、高級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高級、微服務架構方面有深刻的理解