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替换原则是关于继承机制的应用原则,是实现开放封闭原则的具体应用规范。

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

      

  • 相关阅读:
    Codevs 2296 仪仗队 2008年省队选拔赛山东
    Codevs 1535 封锁阳光大学
    Codevs 1069 关押罪犯 2010年NOIP全国联赛提高组
    Codevs 1218 疫情控制 2012年NOIP全国联赛提高组
    Codevs 1684 垃圾陷阱
    洛谷 P1108 低价购买
    Vijos P1325桐桐的糖果计划
    Codevs 3289 花匠 2013年NOIP全国联赛提高组
    Codevs 2611 观光旅游(floyed最小环)
    C语言基础之彩色版C语言(内含linux)
  • 原文地址:https://www.cnblogs.com/maxuefeng/p/15672747.html
Copyright © 2011-2022 走看看