• OO思想举例,控制翻转,依赖注入


     (转自kumaws,原帖地址:http://www.cnblogs.com/kumaws/archive/2009/04/06/from_interface_to_DependencyInjection.html)

        现在在各种技术站点、书籍文章上,都能看到IoC容器、控制反转、依赖注入的字眼,而且还会有一些专门实现这些功能的开发工具等等。那么这种技术是如何演变而来的?它的适用场景是哪里?我们该不该学习并掌握这门技术?下面做出一些解释。

     

    猫狗大战举例


        我现在要做一个猫狗大战的游戏,系统内部采用了标准的OO技术,先设计了一个狗狗的抽象类。

     

     
    public abstract class Dog
    {
        public void Run();
        public void Jump();
        public void Bay();
        public abstract void Display();
    }
     

     假设游戏中每个狗狗跑的速度、跳的高度、叫的音量都是相同的,那么唯一不同的就是外貌了,所以 Display()是抽象的。

     

     
    public class Poodle:Dog
    {
        public override void Display()
        {
            //我是狮子狗
        }
    }

    public class Dachshund:Dog
    {
        public override void Display()
        {
            //我是腊肠狗
        }
    }

    //其他狗狗.
     

          好 了,现在我们想让游戏中加入打斗的元素,狗狗会攻击,可能你会想到只用在基类中加一个Attact()的方法,就可以这样让所有继承它的狗狗就都会攻击 了。不过问题很快就来了,你会发现AIBO(日本产的电子机械宠物狗)包括家里的绒毛玩具狗也会攻击了,这是很不合情理的事情。所以并不是所有的狗狗都会 攻击这个行为,那么有人肯定想到使用接口了,把Attact()这个方法提取到接口中,只让会攻击的狗狗实现这个接口就可以了。

     

     
    public interface IAttact
    {
        void Attact();
    }

    public class Poodle:Dog,IAttact
    {
        public override void Display()
        {
            //我是狮子狗
        }
        
        public void Attact()
        {
            //咬一口
        }
    }
     

     

    说明为什么要针对接口编程,优点

     

    这 样看起来很好,但是需求总是在变化的,现在的需求又增加了:要求每种狗狗的攻击方式不同,有的会咬,有的会扑,有的甚至会狮子吼的功夫,当然如果狗狗升级 了,还会出现更多的攻击方式。上面这个方式的缺点就显现出来了,代码会在多个子类中重复,无法知道所有狗狗的全部行为,运行时不容易改变等等。

        下面这样做,我们把变化的部分提取出来,多种行为的实现用接口统一实现。

     

     
    public class BiteAttact:IAttact
    {
        public void Attact()
        {
             //咬
        }
    }

    public class SnapAttact:IAttact
    {
        public void Attact()
        {
            //扑咬
        }
    }
    //其他实现
     

    当我们想要增加一种行为方式时,只需继承接口就可以,并不会改变其他行为。

     

     
    public class Poodle:Dog
    {
        IAttact attact;

        public Poodle()
        {
            attact=new BiteAttact();
        }
        //这里我在调用的时候就可以动态的设定行为了
        public void SetAttactBehavior(IAttact a)
        {
            attact=a;
        }
        public void PerformAttact()
        {
            attact.Attact();
        }
        
        public override void Display()
        {
            //我是狮子狗
        }
    }
     

     

    这 样的设计就让狗狗的攻击行为有了弹性,我们可以动态的在运行时改变它的行为,也可以随时在不影响其他类的情况下添加更改行为。而以往的做法是行为来自 Dog基类或者继承接口并由子类自行实现,这两种方法都依赖于“实现”,我们被邦的死死的,而无法更改行为。而接口所表示的行为实现,不会被绑死在Dog 类与子类中。这就是设计模式中的一个原则:针对接口编程,不要针对实现编程。

     

    说明为什么要“依赖抽象,不要依赖具体类”

     

    但是,当我们使用“new”的时候,就已经在实例化一个具体类了。这就是一种实现,只要有具体类的出现,就会导致代码更缺乏弹性。就好比雕塑家做好了一个“沉思者”,想把它再改造成“维纳斯”,难度可想而知。

    我们再回到猫狗大战这个游戏,为了增加趣味性,我们增加了猫狗交互的功能,如果你选择了狗狗开始游戏,那么会随机不同的场景,在固定的场景会遇到固定的猫。例如:在峭壁背景会遇到山猫,在象牙塔背景中会遇到波斯猫,在草原中会遇到云猫等。

     

     

    Cat cat;
    if(mountain){
        cat=new Catamountain();
    }else if(ivory){
        cat=new PersianCat();
    }else if(veldt){
        cat=new CloudCat();
    }
     

     

    但 是有时真正要实例化哪些类,要在运行时有一些条件决定。当一旦有变化或扩展时,就要重新打开这段代码进行修改,这就违反了我们“对修改关闭”的原则。这段 代码的依赖特别紧密,而且是高层依赖于低层(客户端依赖具体类的实现)。不过庆幸的是,我们有“依赖倒转原则”与“抽象工厂模式”来拯救我们的代码。

    说明“依赖倒置”与抽象工厂模式


    依 赖倒转原则是要依赖抽象,不要依赖具体类。也就是说客户端(高层组件)要依赖于抽象(Cat),各种猫咪(低层组件)也要依赖抽象(Cat)。虽然我们已 经创造了一个抽象Cat,但我们仍然在代码中实际地创建了具体的Cat,这个抽象并没有什么影响力,那么我们如何将这些实例化对象的代码独立出来?工厂模 式正好派上用场。工厂模式属于创建型模式,它能将对象的创建集中在一起进行处理。

    相反如果你选择了猫咪角色,就会在不同的场景遇到特定的狗狗NPC。

    现在我们要创建一个工厂的接口:

     

     
    public interface Factory
    {
        Cat CreateCat();
        Dog CreateDog();
    }

    public class MountainSceneFactory:Factory
    {
        public Cat CreateCat(){
            return new Catamountain();
        }
        
        public Dog CreateDog(){
            return new CragDog();
        }
    }

    public class VeldtSceneFactory:Factory
    {
        public Cat CreateCat(){
            return new CloudCat();
        }
        
        public Dog CreateDog(){
            return new VeldtDog();
        }
    }
     

     

    然后构建一个草原的场景:

     

     

    public class VeldtScene : Scene 
    {
        Factory factory; 
        private static Cat cat=null;

        private static Dog dog=null;
        public VeldtScene(Factory f) 
        {
            factory=f;
        } 
        Public void prepare()
        { 
            if(User.Identity=="Dog") 
                dog=factory.CreateDog();
            else(user.Identity=="Cat") 
                cat=factory.CreateCat();
        } 

     

    这样一来,场景的条件不由代码来改变,而可以由客户端来动态改变,来看看我们的客户端吧!

     

    Factory factory=new VeldtSceneFactory();
    Scene scene=new VeldtScene(factory);
    Scene.prepare();

     

    这样如果你的角色是一只狗狗的话,就能在这个草原上见到一只云猫了。工厂模式将实例解耦出来,替换不同的工厂以取得不同的行为。


    说明“将组件的配置与使用分离”

           事物总是在发展,需求总是在增加。猫狗大战要升级为网络版,我们希望由开发人员开发游戏,而由技术支持人员做游戏的安装配置。一般开发者会提供配置文件交给 技术支持人员,由他们来动态的为游戏更改配置,例如在草原上出现了波斯猫,老虎却出现在客厅里。技术支持人员是无法修改源代码的,但可以让他们修改配置文 件以来改变实例的创建。

    我 们的设计离不开一个基本原则--分离接口与实现。在面向对象程序里,我们在一个地方用条件逻辑来决定具体实例化哪一个类,以后的条件分支都由多态来实现, 而不是继续重复前面的条件逻辑。当我们决定将“选择具体实现类”的据侧推迟到部署阶段,则在装配原则上是要与应用程序的其余部分分开的,这样我就可以轻松 的针对不同的部署替换不同的配置。

     

    简单说明依赖注入

     

    终于可以进入本文的重点了。现在我们的目标就是要把组件的配置与使用分离开。IoC(Inversion of Control控制反转)容器因此应运而生,martin在他的大作中将此更形象的称谓依赖注入(Dependency Injection)。

          依赖注入的基本思想是:用一个单独的对象获得接口的一个合适的实现,并将其实例赋给调用者的一个字段。具体的依赖注入的讲解可以看Martin Fowler的文章,不再详述。我主要以实例的形式,来更好的理解依赖注入的特点与其所带来的好处。

  • 相关阅读:
    pycharm上运行django服务器端、以及创建app方法
    Python实现淘宝秒杀聚划算自动提醒源码
    Python版:Selenium2.0之WebDriver学习总结_实例1
    windows上使用pip下载东西时报编码错误问题解决方法
    模块购物商城和ATM机代码:
    Python网页信息采集:使用PhantomJS采集淘宝天猫商品内容
    android用户界面之ProgressBar教程实例汇总
    推荐12个亲测Android开发源码(包括应用、游戏、效果等等)
    Android开发各种demo集合
    Android Service
  • 原文地址:https://www.cnblogs.com/zzcc/p/4113920.html
Copyright © 2020-2023  润新知