{"componentChunkName":"component---src-templates-blog-post-js","path":"/blog/2020-06-13-design-principle/","result":{"data":{"markdownRemark":{"id":"484395c8-2d11-5dbc-8e6a-0194d9f22880","html":"<p><img src=\"/img/flavor_wheel.jpg\" alt=\"flavor wheel\"></p>\n<p>高内聚是指相近的功能、行为应该放到同一个组件中。</p>\n<h1>设计原则</h1>\n<h2>高內聚、低耦合</h2>\n<h3>什么是“高内聚”</h3>\n<p>高内聚是指相近的功能、行为应该放到同一个组件中。</p>\n<h3>什么是“低耦合”</h3>\n<p>组件间的依赖关系清晰，交互不复杂。</p>\n<h2>面向对象编程、面向对象分析</h2>\n<p>面向对象编程不是使用面向对象的编程语言进行编程，而是利用多态特性进行编程，面向对象语言真正区别于其他高级语言的地方是<code>多态</code>。</p>\n<h2>框架（frameworks）</h2>\n<p>框架是用来实现某一类应用的结构性程序，是对某一类架构方案可复用的设计与实现。</p>\n<p>架构师通过开发或者维护框架来把控系统的质量。</p>\n<h1>面向对象设计的基本原则</h1>\n<h2>OOD 原则一：开/闭原则（OCP）</h2>\n<p>OCP - Open/Closed Principle</p>\n<ul>\n<li>对扩展是开放的（Open for extension）</li>\n<li>对更改是封闭的（Closed for modification）</li>\n</ul>\n<p>不需要修改现有软件实体，就能实现<code>功能扩展</code>。</p>\n<blockquote>\n<p>实现不修改而扩展的关键是<code>抽象</code>。</p>\n</blockquote>\n<h3>DEMO</h3>\n<p>设计一个控制电话拨号的软件。<br>\n“拨打电话”的 Use Case描述：  </p>\n<ul>\n<li>我们按下数字按钮,屏幕上显示号码,扬声器发出按键音</li>\n<li>我们按下Send按钮,系统接通无线网络,同时屏幕上显示正在拨号。</li>\n</ul>\n<h4>不太优雅的设计</h4>\n<h5>类图</h5>\n<p><img src=\"/img/design-principle/QQ20200616-235325@2x.png\" alt=\"Class Diagram\"></p>\n<h5>Demo Code</h5>\n<pre><code class=\"language-java\">public class Button {\n    public final static int SEND_BUTTON = -99;\n    private Dialer dialer;\n    private int token;\n\n    public Button(int token, Dialer dialer) {\n        this.token = token;\n        this.dialer = dialer;\n    }\n\n    public void press() {\n        switch (token) {\n        case 0:\n        case 1:\n        case 2:\n        case 3:\n        case 4:\n        case 5:\n        case 6:\n        case 7:\n        case 8:\n        case 9:\n            dialer.enterDigit(token);\n            break;\n        case SEND_BUTTON:\n            dialer.dial();\n            break;\n        default:\n            throw new UnsupportedOperationException(\"unknown button pressed: token=\" + token);\n        }\n    }\n}\n</code></pre>\n<h5>坏味道</h5>\n<ul>\n<li>\n<p>僵硬 - 不易增加、修改：</p>\n<ul>\n<li>增加一种Butn类型,就需要对 Button类进行修改; </li>\n<li>修改 Dialer,可能会影响 Button。</li>\n</ul>\n</li>\n<li>\n<p>脆弱- switch case/if elsei语句是相当脆弱的。</p>\n<ul>\n<li>当我想修改Send按钮的功能时,有可能不小心破坏数字按钮</li>\n<li>当这种函数很多时,我很有可能会漏掉某个函数,或其中的某个条件分支。</li>\n</ul>\n</li>\n<li>不可移植-设想我们要设计密码锁的按钮,它只需要数字按键,但 Button的设计使它必须附带”send”类型的按钮。</li>\n</ul>\n<h3>改进 Button：方法一</h3>\n<p>抽象 Button 接口，分别实现 DigitButton 和 SendButton 实现类，分别调用 Dialer 业务方法。Dialer 类内部通过 if-else 判断决定，分别执行 digit/send。</p>\n<p><img src=\"/img/design-principle/QQ20200616-215701@2x.png\" alt=\"method1\"></p>\n<h3>改进 Button：策略模式</h3>\n<p>抽象 ButtonServer 接口，声明 ButtonServer#buttonPressed(int token) 方法，在 Dialer 中实现该方法，供 Button#process 调用。</p>\n<p><img src=\"/img/design-principle/QQ20200616-215836@2x.png\" alt=\"stratery\"></p>\n<p>Button 类伪代码</p>\n<pre><code class=\"language-java\">public class Button {\n    ...\n    public void process(int token) {\n        buttonServer.buttonPressed(token);\n    }\n    ...\n}\n</code></pre>\n<h3>改进 Button：适配器模式</h3>\n<p>定义两个 Adepter 类实现 ButtonServer 接口，分别调用 Dialer 类的 digit/send 方法。Dialer 和 ButtonServer 解耦，去除掉了 if-else 逻辑。</p>\n<p><img src=\"/img/design-principle/QQ20200616-234106@2x.png\" alt=\"adepter\"></p>\n<p>DigitButtonDailerAdepter 类伪代码：</p>\n<pre><code class=\"language-java\">public class DigitbuttonDilerAdepter implements ButtonServer {\n    ...\n\n    @Override\n    public void buttonPressed(int token) {\n        dailer.enterDigit(token);\n    }\n\n    ...\n}\n</code></pre>\n<p>SendButtonDailerAdepter 类伪代码：</p>\n<pre><code class=\"language-java\">public class SendButtonDailerAdepter implements ButtonServer {\n    ...\n\n    @Override\n    public void buttonPressed(int token) {\n        dailer.dail(token);\n    }\n    ...\n}\n</code></pre>\n<p>在此基础上新的需求：<br>\n同一 Button 同时需要触发多个功能。e.g. 点亮电话的灯。</p>\n<p>可以通过增加新的 Adepter 来聚合不同的方法。</p>\n<p>LampAndDigitButtonDailerAdepter 类伪代码：</p>\n<pre><code class=\"language-java\">public class LampAndDigitButtonDailerAdepter implements ButtonServer {\n    ...\n    // enterDigit()\n    // lamp()\n    ...\n}\n</code></pre>\n<h3>改进 Button：观察者模式</h3>\n<p>应对一个 button 按钮需要触发多个功能需求。</p>\n<p>接口 ButtonServer 修改为 ButtonListener。</p>\n<p>Button 类添加 List<ButtonListener> buttonListeners 字段，添加 addButtonListener(ButtonListener buttonListener) 方法。</p>\n<p>Button#process 方法调整为迭代执行 buttonListeners 内所有对象。</p>\n<p><img src=\"/img/design-principle/QQ20200616-234126@2x.png\" alt=\"Observable\"></p>\n<h3>phone 类组装</h3>\n<p>满足开闭原则，对功能扩展有较好的支持。</p>\n<p>Phone 类伪代码：</p>\n<pre><code class=\"language-java\">public class Phone {\n    private Dialer dialer;\n    private Button[] digitButtons;\n    private Button sendButton;\n\n    public Phone() {\n        dialer = new Dialer();\n        digitButtons = new Button[10];\n        for (int i = 0; i &#x3C; digitButtons.length; i++) {\n            digitButtons[i] = new Button();\n            final int digit = i;\n            digitButtons[i].addListener(new ButtonListener() {\n                public void buttonPressed() {\n                    dialer.enterDigit(digit);\n                }\n            });\n        }\n        sendButton = new Button();\n        // 匿名函数定义 Adepter。\n        sendButton.addListener(new ButtonListener() {\n\n            public void buttonPressed() {\n                dialer.dial();\n            }\n\n        });\n    }\n\n    public static void main(String[] args) {\n        Phone phone = new Phone();\n        phone.digitButtons[9].press();\n        phone.digitButtons[1].press();\n        phone.digitButtons[1].press();\n        phone.sendButton.press();\n    }\n\n}\n</code></pre>\n<h1>OOD原则ニ:依赖倒置原则（DIP）</h1>\n<p>DIP - Dependency Inversion Principle</p>\n<p>高层模块不依赖低层模块，而是依赖抽象。</p>\n<p>抽象不依赖实现，而是实现依赖抽象。</p>\n<p>DIP 倒置了什么？</p>\n<ul>\n<li>模块或者包的依赖关系</li>\n<li>开发顺序和职责（高层来定义、来调用）</li>\n</ul>\n<p>软件的层次化</p>\n<ul>\n<li>高层决定低层</li>\n<li><code>高层被重用</code></li>\n</ul>\n<p>e.g. Controller 层定义接口，自己调用， Service 层负责实现这个接口。</p>\n<p><img src=\"/img/design-principle/QQ20200617-093537@2x.png\" alt=\"DIP\"></p>\n<p>反例：</p>\n<p><img src=\"/img/design-principle/QQ20200617-070925@2x.png\" alt=\"unDIP\"></p>\n<p>修改：</p>\n<p><img src=\"/img/design-principle/QQ20200617-070835@2x.png\" alt=\"DIP-demo\"></p>\n<h2>框架的核心</h2>\n<p>好莱坞原则：</p>\n<ul>\n<li>Don't call me, I'll call you.</li>\n</ul>\n<p>倒转的层次依赖关系。</p>\n<p>应用不要调用框架，框架会来调用应用层实现。依赖是反过来的。</p>\n<h1>ooD原则三: Liskova替换原则（LSP）</h1>\n<p>子类必须能替换掉基类。</p>\n<p>凡是使用基类的地方,一定也适用于其子类。</p>\n<p>e.g. 人不能骑马，但是不能骑小马，违反原则。</p>\n<p>子类抛出来的异常是父类抛出来的异常的子类，否则基类 catch 不到子类异常。</p>\n<p>继承违反原则的时候使用组合（适配器模式）。</p>\n<p><img src=\"/img/design-principle/QQ20200617-100430@2x.png\" alt=\"combination\"></p>\n<p><code>如果设计目的不是为了其他类继承的类，最好不要去继承它。</code></p>\n<p>子类不应该比基类语义上更严格。</p>\n<h1>OOD原则四:单一职责原则（SRP）</h1>\n<p>SRP - Single Responsibility Principle</p>\n<p>一个类只有一个引起它改变的原因。</p>\n<p>单个类的职责少一些。</p>\n<p>e.g.</p>\n<p><img src=\"/img/design-principle/QQ20200617-102808@2x.png\" alt=\"x\"></p>\n<p>改进</p>\n<p><img src=\"/img/design-principle/QQ20200617-102824@2x.png\" alt=\"x\"></p>\n<h2>区分类的方法:分清职责</h2>\n<p>职责: 变化的原因</p>\n<p>如果实现类不可拆分，采用接口隔离，分离职责。</p>\n<h1>OOD原则五:接口分离原则(ISP)</h1>\n<p>ISP - Interface Segregation Principle</p>\n<p>不应该强迫客户程序依赖它们不需要的方法。</p>\n<p>客户端看不到不需要的方法。</p>\n<p>不暴露给客户端不需要的方法。</p>\n<p>不要为了复用方法继承基类。</p>\n<p>ISP和SRP的关系</p>\n<ul>\n<li>ISP和SRP是相关的,都和“内聚性”有关。</li>\n<li>SRP指出应该如何设计ー一个类ーー只能有一种原因才能促使类发生改变。</li>\n<li>ISP指出应该如何设计一个接口ーー从客户的需要出发,强调不要让客户看到他们不需要的方法。</li>\n</ul>\n<h1>总结</h1>\n<p>OOD 原则</p>\n<ul>\n<li>开/闭原则（OCP）</li>\n<li>依赖倒置原则（DIP）</li>\n<li>Liskova替换原则（LSP）</li>\n<li>单一职责原则（SRP）</li>\n<li>接口分离原则（ISP）</li>\n</ul>","frontmatter":{"date":"June 13, 2020","title":"面向对象设计原则","description":"面向对象设计原则","tags":["OOD Principle"]}}},"pageContext":{"id":"484395c8-2d11-5dbc-8e6a-0194d9f22880"}}}