zoukankan      html  css  js  c++  java
  • OO大原则

    系统软件的常见问题

      1:僵化:牵一发而动全身,系统不可修改或扩展。

       2:复杂或重复。过分复杂,难于理解。

             3:不可复用,过于僵化而不可服用,不能剥离出独立的服用组件。

       4:不够稳定。常常出错而又无法解决问题,系统运行不够可靠。

    设计的原则:

      降低耦合,来实现软件的复用和扩展,这正是设计原则的最终奥义。

           随着面向对象的发展,形成了以封装,继承,多态为主的完整体系。继承了以抽象来封装变化,降低耦合实现复用的精髓。

          而设计模式是对经验的总结与提炼,是对重复发生问题进行的总结和最佳解决策略的探索。

    经典的5个设计原则:

      1:单一职责原则

          一个类,应该仅有一个引起它变化的原因。不要将变化原因不同的职责封装在一起,而应该分离。

      2:开放封闭原则

          软件实体,应该对外修改关闭,对外扩展开放。

      3:依赖倒置原则

          依赖于抽象,而不要依赖于具体,因为抽象相对稳定。

      4:Liskov替换原则

          子类必须能够替换其基类

       5:合成/聚合复用原则

          在新对象中聚合已有对象,使之成为新对象的成员,从而通过操作这些对象达到复用的目的,合成方式较继承方式耦合更松散。所以应该少继承,多聚合。

       6:迪米特法则

          又叫最少知识原则,指软件实体应该尽可能少的和其他软件实体发生作用。

    单一职责原则的核心思想:

           一个类最好只做一件事,只有一个引起它变化的原因。

            单一职责原则可以看作是低耦合、高内聚在面向对象原则上的引申,将职责定义为引起变化的原因,以提高内聚性来减少引起变化的原因。职责过多,引起他变化的原因就越多,这将导致职责依赖,相互之间产生影响。从而极大的损伤其内聚性和耦合度。单一职责,通常意味着单一的功能,因此不要为类实现过多的功能点,以保证实体只有一个引起它变化的原因。

          因此,SRP原则的核心就是要求对类的改变只能是一个,对于违反这一原则的类应该进行重构。

     在以上设计中,DBManager类将对数据库的操作和用户权限的判别封装在一个类中实现,已添加记录为例。

      

    public class DBManager
    {
        public void Add()
        {
            if (GetPermission(id) == "CanAdd") {
                Console.WriteLine("管理员可以增加数据。");
            }    
        }
    }

    这显然是一个充满僵化味道的实现,如果权限设置的规则发生改变,那么必须修改所有的数据库操作逻辑。

    重新设计的思路:

      

     以Proxy模式调整之后,有效实现了职责的分离,DBManager类只关注数据操作和逻辑,而不用关系权限判断逻辑。

      

    public class DBManager:IDBAction
    {
        public void Add()
        {
              
        }
    }

    而将权限的判断交给DBManagerProxy代理类来完成。

      

    public class DBManagerProxy : IDBAction
    {
        private IDBACtion dbManager;
        public DBManagerProxy(IDBAction dbAction)
        {
            dbManager = dbAction;   
        }
    
        //处理权限判断的逻辑
        public string GetPermission(string id)
        {
            //处理权限判断
        }
    
        public void Add()
        {
            if (GetPermission(id) == "CanAdd") {
                    dbManager.Add();    
            }
        }
    }

    通过代理,将数据操作和权限判断两个职责分离,而实际的数据操作由DBManager来执行,此时客户端的调用就变得非常简单。

    public class DBClient
    {
        public static void Main()
        {
            IDBAction DBManager = new DBManagerProxy(new DBManager("CanAdd"));
            DBManager.Add();    
        }
    }

     开放封闭原则:

          开放封闭原则(OCP)是面向对象原则的核心,软件设计本身的目标就是封装变化,降低耦合。

          核心思想:

              软件实体应该是可拓展,而不可修改的。也就是说,对拓展是开放的,而对修改是封闭的。

          因此开放封闭原则主要体现在2个方面:

              对外拓展开放,意味着有新的需求或变化时,可以对现有代码进行拓展,以适应新的情况。

              对修改封闭,意味着一旦设计完成,就可以完成独立完成其工作,而不要对类进行任何修改。

          需求总是变化。对软件设计者来说,必须在不需要原有系统进行修改的情况下,实现灵活的系统拓展,只有依赖于抽象。实现开放封闭的原则就是对抽象编程,而不是对具体编程,因为抽象相对稳定,而通过面向对象的继承和多态机制,可以实现对抽象体的继承,通过覆写其方法来改变固有行为,实现新的拓展方法,所以对拓展就是开放的,这是实施开放封闭原则的基本思路。

    应用反思:

        

    思路:银行处理员

      

    class BusyBankStaff
    {
        private BankProcess bankProc = new BankProcess();
    
        //定义银行业务员工的业务操作
        public void HandleProcess(Client client)
        {
            switch (client.ClientType) {
                case "存款用户":
                    bankProce.Deposit();
                    break;
                case "转款用户":
                    bankProce.Transfer();
                    break;
                case "取款用户":
                    bankProce.DrawMoney();
                    break;
            }
    }

    将业务功能抽象为接口,当业务员依赖于固定的抽象时,对于修改就是封闭的,而通过继承和多态机制,从抽象体派生出新的拓展思路,就是对拓展的思路。

     

     

     按照上述设计实现,用细节体现为:

      

    interface IBankProcess
        {
            void Process();
        }
    
    //按银行按业务进行分类
        class DespositProcess : IBankProcess
        {
            public void Process()
            {
                //办理存款业务
            }
        }
    
    
        class TransferProcess : IBankProcess
        {
           public void Process()
            {
                //办理转账业务
            }
        }
    
        class DrawMoneyProcess : IBankProcess
        {
            public void Process()
            {
                //办理取款业务
            }
        }

    思路的转变,会让复杂的问题变得简单,使系统各负其责,人人实惠。有了上述的重构,银行工作人员变成一个彻底的EasyBankStaff.

      

    class EasyBankStaff
        {
            private IBankProcess bankProc = null;
    
            public void HandleProcesss(ClientCertificateOption client) {
                //业务处理
                bankProc = client.CreateProcess();
                bankProc.Process();
                    }
        }

    银行业务可以像这样就自动的实现了。

      

      class BankProcess
        {
            public static void Main()
            {
                EasyBankStaff bankStaff = new EasyBankStaff();
                bankStaff.HandleProcesss(new Client("转账用户"));
            }
        }

    当有新的业务增加时,银行经理不必为重新组织业务流程而担心,只需为新增的业务实现IBankProcess接口,系统的其他部分丝毫不受影响。

     对应的实现为:

      

      class FundProcess : IBankProcess
        {
            public void Process()
            {
                //办理基金业务
            }
        }

     依赖倒置原则:

        依赖倒置原则核心思想:

            依赖于抽象。

        具体而言:

            高层模块不应该依赖于底层模块,两者都应该依赖于抽象。抽象不应该依赖于具体,具体应该依赖于抽象。

        依赖,一定会存在于类与类、模块与模块之间。面向对象设计在某种层次上,就是一个关于关系处理的哲学,而依赖倒置正是这种哲学思想在具体应用中的体现。当两个模块之间存在紧耦合关系时,最好的方法就是分离接口和实现,使得高层调用接口的方法,底层模块实现接口的定义。

        同时,业务员EasyBankStaff、业务IBankProcess和客户Client之间,明显违背了依赖倒置原则,业务员和业务类依赖于具体的客户,而非抽象。

        

         CreateProcess在创建业务类别时,是必须依托于ClientType为判断条件的,从而也决定了HandleProcess的执行也受制于ClientType的条件时,我们必须从HandleProcess的处理过程了解这一点。

    public class Program
    {
        public static void Main(string[] args)
        {
            EasyBankStaff bankStaff = new EasyBankStaff();
            bankStaff.HandleProcess(new Client("转账用户"));
        }
    }

      bankStaff处理HandleProcess的过程依赖于具体的Client客户,而当有新的业务类别增加时,系统中必须增加对客户类别的依赖,对于完美的设计来说,这种机制是僵化的,应该实现更好的解决方案。

    需要找到潜在的对象,使EasyBankStaff依赖于抽象,而抽象的办法就是为EasyBankStaff和Client之间增加一个抽象接口。

     具体的实现为:

      

    interface IClient
    {
        IBankProcess CreateProcess();
    }
    
    class DepositClient : IClient
    {
        IBankProcess IClient.CreateProcess()
        {
            return new DepositProcess();
        }
    }
    
    class TransferClient : IClient
    {
        IBankProcess IClinet.CreateProcess()
        {
            return new TransferProcess();
        }
    }
    
    class DrawMoneyClient : IClient
    {
        IBankProcess IClinet.CreateProcess()
        {
            return new DrawMoneyProcess();
        }
    }

    在客户端调用,不需要进行任何类别的判断,可以实现用户自动找到窗口的需求。

    public class BankProcess
    {
        public static void Main()
        {
            EasyBankStaff bankStaff = new EasyBankStaff();
            bankStaff.HandleProcess(new Client("转账用户"));
        }
    }

    HandleProcess自行受理其业务,通过依赖于抽象,实现了Client对象的依赖倒置,Client不被依赖,可以实现更多的灵活性。当有新的业务类别增加,只需要实现Client接口。

    class FundClient : IClinet
    {
        IBankProcess IClient.CreateProcess()
        {
            return new FundProcesss();
        }
    }

      抽象的稳定性决定了系统的稳定性,因为抽象是保持不变的,依赖于抽象是面向对象设计的精髓,也是依赖倒置原则的核心思想。

      依赖于抽象是一个通用的规则,而某些时候依赖于细节则是在所难免的,必须权衡在抽象和具体之间的取舍,方法不是一成不变的。

      依赖于抽象,就是要对接口编程,不要对实现编程。

    接口隔离原则:

      核心思想:

          使用多个小的专门的接口,而不要使用一个大的总接口。  

      具体实现:

          接口应该是内聚的,应该避免出现"胖"接口。

          一个类对另一个类的依赖应该建立在最小的接口上,不要强迫依赖不用的接口,这是一种接口的污染。

      接口有效地将细节和抽象隔离,体现了对抽象编程的一切好处,接口隔离原则强调接口的单一性。而胖接口存在明显的弊端,会导致实现的类型必须完全实现接口所有的方法、属性等。在设计上,这是一种浪费,而且在实施上会带来潜在的问题,对胖接口的修改将导致一连串的客户端程序需要修改,这是一种灾难。

      将胖接口分解为多个特定的定制化方法,使得客户端仅仅依赖于他们实际调用的方法,从而解除了客户端不会依赖于他们不用的方法。因此,按照客户需求将客户分组,并依赖这种分组来实现接口,是接口隔离的重要方法。分离的主要手段有两种:

      委托分离,通过增加一个新的类型来委托客户的请求,隔离客户和接口的直接依赖,但是会增加系统开销。

      多重继承分离,通过接口多继承来实现客户需求,值得推荐。

    应用反思:

      

     

      IComputerUser是一个典型的"胖"接口,对于Aduit来说,他既需要工作,又需要娱乐,对于学生,只需要学习就行了,工作对他来说是浪费。

      重新设计:

        

     

    interface IComputerLearn
    {
        void ToLearn();
    }
    
    interface IComputerWork
    {
        void ToWork();
    }
    
    interface IComputerBeFun
    {
        void ToBeFun();
    }
    
    class Aduit
    {
        private IComputerWork myWork();
        private IComputerBeFun myFun;
    
        public void UseComputer
        {
            //主要是工作
            myWork.ToWork();
            //还可以娱乐
            myFun.ToBeFun();
        }
    }
    
    class Child
    {
        private IComputerLearn myLearn;
    
        public void UseComputer()
        {
            myLearn.ToLearn();
        }
    }

    建议:

      将功能接近的接口合并,可能造成接口污染,实现内聚的接口才是接口设计的基本原则。

      接口隔离原则,能够保证系统拓展和修改的影响不会拓展到系统的其他部分,

    Liskov替换原则:

      Liskov替换原则是关于继承机制的应用原则,是实现开放封闭原则的具体应用规范。

      只有子类能够替换其基类时,才能保证系统在运行时期内识别子类,这是保证继承复用的基础。

      

  • 相关阅读:
    前端:js
    HTML和CSS总结
    前端二:CSS
    前端一:走进HTML
    SQLALchemy(连表)、paramiko
    上下文管理、线程池、redis订阅和发布
    P4234 最小差值生成树
    P2387 [NOI2014]魔法森林
    P3721 [AH2017/HNOI2017]单旋
    P4271 [USACO18FEB]New Barns
  • 原文地址:https://www.cnblogs.com/maxuefeng/p/15672747.html
Copyright © 2011-2022 走看看