SOLID SOLID 是几个单词首字母组合而来,分别表示 单一功能原则、开闭原则、里氏替换原则、接口隔离原则以及依赖反转原则。
单一功能原则 如果一个类干的事情太多太杂,会导致后期很难维护。我们应该厘清职责,各司其职减少相互之间依赖。
Bad:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class UserSettings { constructor (user) { this .user = user; } changeSettings(settings) { if (this .verifyCredentials()) { } } verifyCredentials() { } }
Good:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class UserAuth { constructor (user) { this .user = user; } verifyCredentials() { } } class UserSetting { constructor (user) { this .user = user; this .auth = new UserAuth(this .user); } changeSettings(settings) { if (this .auth.verifyCredentials()) { } } }
开闭原则 “开”指的就是类、模块、函数都应该具有可扩展性,“闭”指的是它们不应该被修改。也就是说你可以新增功能但不能去修改源码。
Bad:
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 AjaxAdapter extends Adapter { constructor () { super (); this .name = 'ajaxAdapter' ; } } class NodeAdapter extends Adapter { constructor () { super (); this .name = 'nodeAdapter' ; } } class HttpRequester { constructor (adapter) { this .adapter = adapter; } fetch(url) { if (this .adapter.name === 'ajaxAdapter' ) { return makeAjaxCall(url).then((response ) => { }); } else if (this .adapter.name === 'httpNodeAdapter' ) { return makeHttpCall(url).then((response ) => { }); } } } function makeAjaxCall (url ) { } function makeHttpCall (url ) { }
Good:
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 class AjaxAdapter extends Adapter { constructor () { super (); this .name = 'ajaxAdapter' ; } request(url) { } } class NodeAdapter extends Adapter { constructor () { super (); this .name = 'nodeAdapter' ; } request(url) { } } class HttpRequester { constructor (adapter) { this .adapter = adapter; } fetch(url) { return this .adapter.request(url).then((response ) => { }); } }
里氏替换原则 名字很唬人,其实道理很简单,就是子类不要去重写父类的方法。
Bad:
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 50 51 52 class Rectangle { constructor () { this .width = 0 ; this .height = 0 ; } setColor(color) { } render(area) { } setWidth(width) { this .width = width; } setHeight(height) { this .height = height; } getArea() { return this .width * this .height; } } class Square extends Rectangle { setWidth(width) { this .width = width; this .height = width; } setHeight(height) { this .width = height; this .height = height; } } function renderLargeRectangles (rectangles ) { rectangles.forEach((rectangle ) => { rectangle.setWidth(4 ); rectangle.setHeight(5 ); const area = rectangle.getArea(); rectangle.render(area); }); } const rectangles = [new Rectangle(), new Rectangle(), new Square()];renderLargeRectangles(rectangles);
Good:
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 class Shape { setColor(color) { } render(area) { } } class Rectangle extends Shape { constructor (width, height) { super (); this .width = width; this .height = height; } getArea() { return this .width * this .height; } } class Square extends Shape { constructor (length) { super (); this .length = length; } getArea() { return this .length * this .length; } } function renderLargeShapes (shapes ) { shapes.forEach((shape ) => { const area = shape.getArea(); shape.render(area); }); } const shapes = [new Rectangle(4 , 5 ), new Rectangle(4 , 5 ), new Square(5 )];renderLargeShapes(shapes);
接口隔离原则 JavaScript 几乎没有接口的概念,所以这条原则很少被使用。官方定义是“客户端不应该依赖它不需要的接口”,也就是接口最小化,把接口解耦。
Bad:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class DOMTraverser { constructor (settings) { this .settings = settings; this .setup(); } setup() { this .rootNode = this .settings.rootNode; this .animationModule.setup(); } traverse() { } } const $ = new DOMTraverser({ rootNode: document .getElementsByTagName('body' ), animationModule() {} });
Good:
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 class DOMTraverser { constructor (settings) { this .settings = settings; this .options = settings.options; this .setup(); } setup() { this .rootNode = this .settings.rootNode; this .setupOptions(); } setupOptions() { if (this .options.animationModule) { } } traverse() { } } const $ = new DOMTraverser({ rootNode: document .getElementsByTagName('body' ), options: { animationModule() {} } });
依赖反转原则 说就两点:
高层次模块不能依赖低层次模块,它们依赖于抽象接口。
抽象接口不能依赖具体实现,具体实现依赖抽象接口。
总结下来就两个字,解耦。
Bad:
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 class InventoryRequester { constructor () { this .REQ_METHODS = ['HTTP' ]; } requestItem(item) { } } class InventoryTracker { constructor (items) { this .items = items; this .requester = new InventoryRequester(); } requestItems() { this .items.forEach((item ) => { this .requester.requestItem(item); }); } } const inventoryTracker = new InventoryTracker(['apples' , 'bananas' ]);inventoryTracker.requestItems();
Good:
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 InventoryTracker { constructor (items, requester) { this .items = items; this .requester = requester; } requestItems() { this .items.forEach((item ) => { this .requester.requestItem(item); }); } } class InventoryRequesterHTTP { constructor () { this .REQ_METHODS = ['HTTP' ]; } requestItem(item) { } } class InventoryRequesterWS { constructor () { this .REQ_METHODS = ['WS' ]; } requestItem(item) { } } const inventoryTracker = new InventoryTracker(['apples' , 'bananas' ], new InventoryRequesterHTTP());inventoryTracker.requestItems();