精华内容
下载资源
问答
  • 面向对象软件设计遵循的原则 1.单一职责原则(SRP--Single-Responsibility Principle) SRP简介:就一个类而言,应该只专注于做一件事和仅有一个引起它变化的原因。 所谓职责,我们可以理解他为功能,就是设计的...

    面向对象软件设计遵循的原则

    1.单一职责原则(SRP--Single-Responsibility Principle

    SRP简介:就一个类而言,应该只专注于做一件事和仅有一个引起它变化的原因。

    所谓职责,我们可以理解他为功能,就是设计的这个类功能应该只有一个,而不是两个或更多。也可以理解为引用变化的原因,当你发现有两个变化会要求我们修改这个类,那么你就要考虑撤分这个类了。因为职责是变化的一个轴线,当需求变化时,该变化会反映类的职责的变化。

    使用SRP注意点:

    1、一个合理的类,应该仅有一个引起它变化的原因,即单一职责; 

    2、在没有变化征兆的情况下应用SRP或其他原则是不明智的; 

    3、在需求实际发生变化时就应该应用SRP等原则来重构代码; 

    4、使用测试驱动开发会迫使我们在设计出现臭味之前分离不合理代码; 

    5、如果测试不能迫使职责分离,僵化性和脆弱性的臭味会变得很强烈,那就应该用 FacadeProxy模式对代码重构;

    SRP优点:

    消除耦合,减小因需求变化引起代码僵化性臭味

    2.开放-封闭原则

    全称:“Open-Closed Principle” 开放-封闭原则

    说明:对扩展开放,对修改关闭。

    优点:按照OCP原则设计出来的系统,降低了程序各部分之间的耦合性,其适应性、灵活性、稳定性都比较好。当已有软件系统需要增加新的功能时,不需要对作为系统基础的抽象层进行修改,只需要在原有基础上附加新的模块就能实现所需要添加的功能。增加的新模块对原有的模块完全没有影响或影响很小,这样就无须为原有模块进行重新测试。

    如何实现“开-闭”原则

    在面向对象设计中,不允许更改的是系统的抽象层,而允许扩展的是系统的实现层。换言之,定义一个一劳永逸的抽象设计层,允许尽可能多的行为在实现层被实现。

    解决问题关键在于抽象化,抽象化是面向对象设计的第一个核心本质。

    对一个事物抽象化,实质上是在概括归纳总结它的本质。抽象让我们抓住最最重要的东西,从更高一层去思考。这降低了思考的复杂度,我们不用同时考虑那么多的东西。换言之,我们封装了事物的本质,看不到任何细节。

    在面向对象编程中,通过抽象类及接口,规定了具体类的特征作为抽象层,相对稳定,不需更改,从而满足“对修改关闭”;而从抽象类导出的具体类可以改变系统的行为,从而满足“对扩展开放”。对实体进行扩展时,不必改动软件的源代码或者二进制代码。关键在于抽象。

    3.里氏代换原则

    全称:“Liskov Substitution Principle” 里氏代换原则

    说明:子类型必须能够替换它们的基类型。一个软件实体如果使用的是一个基类,那么当把这个基类替换成继承该基类的子类,程序的行为不会发生任何变化。软件实体察觉不出基类对象和子类对象的区别。

    优点:可以很容易的实现同一父类下各个子类的互换,而客户端可以毫不察觉。

    4.依赖倒置原则

    全称:“Dependence Inversion Principle”依赖倒置原则

    说明:要依赖于抽象,不要依赖于具体。客户端依赖于抽象耦合。抽象不应当依赖于细节;细节应当依赖于抽象;要针对接口编程,不针对实现编程。

    优点:使用传统过程化程序设计所创建的依赖关系,策略依赖于细节,这是糟糕的,因为策略受到细节改变的影响。依赖倒置原则使细节和策略都依赖于抽象,抽象的稳定性决定了系统的稳定性。

    怎样做到依赖倒置?

    以抽象方式耦合是依赖倒转原则的关键。抽象耦合关系总要涉及具体类从抽象类继承,并且需要保证在任何引用到基类的地方都可以改换成其子类,因此,里氏代换原则是依赖倒转原则的基础。

    在抽象层次上的耦合虽然有灵活性,但也带来了额外的复杂性,如果一个具体类发生变化的可能性非常小,那么抽象耦合能发挥的好处便十分有限,这时可以用具体耦合反而会更好。

    层次化:所有结构良好的面向对象构架都具有清晰的层次定义,每个层次通过一个定义良好的、受控的接口向外提供一组内聚的服务。

    依赖于抽象:建议不依赖于具体类,即程序中所有的依赖关系都应该终止于抽象类或者接口。尽量做到:

    1、任何变量都不应该持有一个指向具体类的指针或者引用。

    2、任何类都不应该从具体类派生。

    3、任何方法都不应该覆写它的任何基类中的已经实现的方法。

    5.接口隔离原则

    全称:“Interface Segregation Principle” 接口隔离原则

    说明:使用多个专一功能的接口比使用一个的总接口总要好。从一个客户类的角度来讲:一个类对另外一个类的依赖性应当是建立在最小接口上的。过于臃肿的接口是对接口的污染,不应该强迫客户依赖于它们不用的方法。

    优点:会使一个软件系统功能扩展时,修改的压力不会传到别的对象那里。

    如何实现接口隔离原则

    不应该强迫用户依赖于他们不用的方法。

    1、利用委托分离接口。

    2、利用多继承分离接口。

    6. 合成复用原则

    全称:“Composite/Aggregate Reuse Principle” 合成/聚合复用原则 or Composite Reuse Principle” 合成复用原则

    说明:如果新对象的某些功能在别的已经创建好的对象里面已经实现,那么尽量使用别的对象提供的功能,使之成为新对象的一部分,而不要自己再重新创建。新对象通过向这些对象的委派达到复用已有功能的。

    简而言之,要尽量使用合成/聚合,尽量不要使用继承。

    优点:

    1) 新对象存取成分对象的唯一方法是通过成分对象的接口。

    2) 这种复用是黑箱复用,因为成分对象的内部细节是新对象所看不见的。

    3) 这种复用支持包装。

    4) 这种复用所需的依赖较少。

    5) 每一个新的类可以将焦点集中在一个任务上。

    6) 这种复用可以在运行时间内动态进行,新对象可以动态的引用与成分对象类型相同的对象。

    7) 作为复用手段可以应用到几乎任何环境中去。

    缺点:

    就是系统中会有较多的对象需要管理。

    7.迪米特原则

    全称:“Law of Demeter” 迪米特原则 or Least Knowledge Principle” 最少知识原则

    说明:对象与对象之间应该使用尽可能少的方法来关联,避免千丝万缕的关系。

    如何实现迪米特法则

    迪米特法则的主要用意是控制信息的过载,在将其运用到系统设计中应注意以下几点:

    1) 在类的划分上,应当创建有弱耦合的类。类之间的耦合越弱,就越有利于复用。

    2) 在类的结构设计上,每一个类都应当尽量降低成员的访问权限。一个类不应当public自己的属性,而应当提供取值和赋值的方法让外界间接访问自己的属性。

    3) 在类的设计上,只要有可能,一个类应当设计成不变类。

    4) 在对其它对象的引用上,一个类对其它对象的引用应该降到最低。

    展开全文
  • 面向对象软件设计”这个术语及其相关话题对于很多开发人员来说已经是耳熟能详了,甚至听腻了。但是,对不住各位,为了吸引眼球和引起“异性” 注意,本座还是落入俗套选择这个热门话题作为本博客的开张炒作篇目。...
    “面向对象软件设计”这个术语及其相关话题对于很多开发人员来说已经是耳熟能详了,甚至听腻了。但是,对不住各位,为了吸引眼球和引起“异性” 注意,本座还是落入俗套选择这个热门话题作为本博客的开张炒作篇目。请见谅,也希望各位看官能放下架子,略读一下本系列的所有文章再喷,可赞可弹可BS, 但勿吐槽,毕竟不想因为本座的文字噪音降低看官的SZ,伤不起呀!!


      言归正传,在如何阐述“面向对象软件设计”这个问题上,本座琢磨了良久,毕竟命题宽泛,表达方式和层次多样。最终决定用相对通俗,以本座的读书和工作经历,在中等层次上进行阐述。


      本座从十年多前走出校门之时就确信软件架构(设计)、数据结构和算法是软件的根本核心,也是衡量个人技术水平的终极标准。因此,从一开始本座就特别关注软件设计方面的知识,本座读过的三本书恰巧体现了软件设计开发的几个方面:


    编程开发:《Design Patterns Elements of Reusable Object-Oriented Software》
    软件设计:《Agile Software Development: Principles, Patterns, and Practices》
    系统架构:《Pattern-Oriented Software Architecture》
        若从编程开发的层次谈软件设计不够全面,有只见树木不见森林之嫌;而从系统架构的层次去谈则显得过于专业,受众不多,会失去大部分读者。因此本系列文 章通过本座多年前阅读:《Agile Software Development》的笔记整理和心得体会来阐述面向对象软件设计原则。


      最后,本座要表达一个重要的观点:任何知识的学习过程都要经历“守”(依葫芦画瓢,获得感觉)、“破”(掌握规律,懂得比较、选择)、“离”(突破条框,自成一派,无招胜有招)三个阶段,在学习的过程中不要墨守成规,需要结合实际情况不断思考和提炼知识,突破自己。


    自信来源于哪里?来源于掌握足够多的知识。
    求知欲望来源于哪里?来源于未掌握的知识。知识就像一个圆圈,掌握知识越多圆圈越大,圆圈越大则圈外的世界也越大。
    展开全文
  • 面向对象设计不就是OOD吗?不就是用C++、Java、Smalltalk等面向对象语言写程序吗?不就是封装+继承+多态吗?  很好!大家已经掌握了不少对面向对象设计的基本要素:开发语言、基本概念、机制。Java是一种纯面向...

      提起面向对象,大家也许觉得自己已经非常“精通”了,起码也到了“灵活运用”的境界。面向对象设计不就是OOD吗?不就是用C++、Java、Smalltalk等面向对象语言写程序吗?不就是封装+继承+多态吗?

      很好!大家已经掌握了不少对面向对象设计的基本要素:开发语言、基本概念、机制。Java是一种纯面向对象语言,是不是用Java写程序就等于面向对象了呢?我先列举一下面向对象设计的11个原则,测试一下大家对面向对象设计的理解程度~^_^~

     

    • 单一职责原则(The Single Responsibility Principle,简称SRP)
    • 开放-封闭原则(The Open-Close Principle,简称OCP)
    • Liskov替换原则(The Liskov Substitution,简称LSP)
    • 依赖倒置原则(The Dependency Inversion Principle,简称DIP)
    • 接口隔离原则(The Interface Segregation Principle,简称ISP)
    • 重用发布等价原则(The Reuse-Release Equivalence Principle,简称REP)
    • 共同重用原则(The Common Reuse Principle,简称CRP)
    • 共同封闭原则(The Common Close Principle,简称CCP)
    • 无环依赖原则(The No-Annulus Dependency Principle,简称ADP)
    • 稳定依赖原则(The Steady Dependency Principle,简称SDP)
    • 稳定抽象原则(The Steady Abstract Principle,简称SAP)

     

      其中1-5的原则关注所有软件实体(类、模块、函数等)的结构和耦合性,这些原则能够指导我们设计软件实体和确定软件实体的相互关系;6-8的原则关注包的内聚性,这些原则能够指导我们对类组包;9-11的原则关注包的耦合性,这些原则帮助我们确定包之间的相互关系。


     

    1 单一职责原则(SRP)

    就一个类而言,应该仅有一个引起它变化的原因。

     

      在SRP中,我们把职责定义为“变化的原因”。如果你能够想到多于一个动机去改变一个类,那么这个类就具有多于一个的职责。有时,我们很难注意到这一点,我们习惯于以组的形式去考虑职责。

    1.1 Rectangle类

      例如,图2.1-1,Rectangle类具有两个方法,一个方法把矩形绘制在屏幕上,另一个方法计算矩形面积。

    图2.1-1 多于一个的职责

     

       有两个不同的应用程序使用Rectangle类。一个是有关计算几何学方面的,Rectangle类会在几何形状计算方面为它提供帮助,它从来不会在屏 幕上绘制矩形。另一个应用程序是有关图形绘制方面的,它可能进行一些几何学方面的工作,但是它肯定会在屏幕上绘制矩形。

      这个设计违反了SRP。Rectangle类具有两个职责。第一个职责提供了矩形几何形状数学模型;第二个职责是把矩形在一个图形用户界面上绘制出来。

      对于SRP的违反导致了一些严重的问题。首先,我们必须在计算几何应用程序中包含GUI代码。如果这是一个C++程序,就必须要把GUI代码链接进来,这会浪费链接时间、编译时间以及内存占用。如果是一个JAVA程序,GUI的.class文件必须要部署到目标平台。

       其次,如果Graphical Application的改变由于一些原因导致了Rectangle的改变,那么这个改变会迫使我们重新构建、测试已经部署Computational Geometry Application。如果忘记了这样作,Computational Geometry Application可能会以不可预测的方式失败。

      一个较好的设计是把这两个职责分离到图2.1-2中所示的两个完全不同的类中。这 个设计把Rectangle类中进行计算的部分移到GeometryRectangle类中,现在矩形绘制方式 的改变不会对Computational Geometry Application造成影响。 

     

    图2.1-2 分离的职责

    1.2 结论

      SRP是所有原则中最简单的原则之一,也是最难正确运用的原则之一。我们会自然地把职责结合在一起。软件设计真正要做到的许多内容,就是发现职责,并把那些职责相互分离。事实上,我们要论述的其余原则都会以这样或那样的方式回到这个问题上。


     

    2 开放-封闭原则(OCP)

    软件实体(类、模块、函数等)应该是可以扩展的,但是不可修改的。

     

      遵循OCP设计出的模块具有两个主要的特征:

      1、  对于扩展是开放的(Open for extension)

      这意味着模块的行为是可以扩展的。当应用的需求变化时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。换句话说,我们可以改变模块的功能。

      2、  对于更改是封闭的(Closed for modification)

      对模块行为进行扩展时,不必改动模块的源代码或者二进制代码。模块的二进制可执行版本,无论是共享库、dll或者Java的jar文件,都无需改动。

     

      这两个特征好像是相互矛盾的。扩展模块行为的通常方式就是修改模块的源代码。不允许修改的模块常常都被认为是具有固定的行为。怎样可能在不改动模块源代码的情况下去更改它的行为呢?怎样才能在无需对模块进行改动的情况下就改变它的功能呢?——关键是抽象!

    2.1 Shape应用程序

      我们有一个需要在标准GUI上绘制圆和正方形的应用程序。

    2.1.1 违反OCP

    程序2.2.1.1-1 Square/Circle问题的过程化解决方案

    ------------------------------shape.h------------------------------
    
    enum ShapeType {circle, square };
    
    struct Shape
    {
           ShapeType itsType;
    }
    
    ------------------------------circle.h------------------------------
    
    #include shape.h
    
    struct Circle
    {
           ShapeType itsType;
           double itsRadius;
           Point itsCenter;
    };
    
    ------------------------------square.h------------------------------
    
    #include shape.h 
    
    struct Aquare
    {
           ShapeType itsType;
           double itsSide;
           Point itsTopLeft;
    };
    
    ------------------------------drawAllShapes.c------------------------------
    
    #include shape.h
    #include circle.h
    #include square.h
    
    typedef struct Shape* ShapePointer;
    
    Void DrawAllShapes(ShapePointer list[], int n)
    
    {
    
           int i;
    
           for (i = 0; i < n; i++)
          {
                  struct Shape* s = list[i];
    
                  switch (s->itsType)
                  {
                         case square:
                                DrawSquare((struct Square*) s );
                                Break;
    
                         case circle:
                                DrawCircle((struct Circle*) s );
                                Break;
                  }
           }
    }

     

      DrawAllShapes函数不符合OCP,因为它对于新的形状类型的添加不是封闭的。如果希望这个函数能够绘制包含有三角形的列表,就必须更改这个函数。事实上每增加一种新的形状类型,都必须要更改这个函数。

      同样,在进行上述改动时,我们必须要在ShapeType enum中添加一个新的成员。由于所有不同种类的形状都依赖于这个enum的声明,所有我们必须要重新编译所有的形状模块。并且也必须要重新编译所有依赖于Shape类的模块。

     

       程序2.2.1.1-1中的解决方案是僵化的,这是因为增加Triangle会导致Shape、Square、Circle以及 DrawAllShapes的重新编译和重新部署。该方法是脆弱的,因为很可能在程序的其他地方也存在类似的既难以查找又难以理解的 switch/case或者if/else语句。该方法是牢固的,因为想在另一个程序中复用DrawAllShapes时,都必须附带上Square和 Circle,即使那个新程序不需要它们。因此该程序展示了许多糟糕设计的臭味。

    2.1.2 遵循OCP

    程序2.2.1.2-1 Square/Circle问题的OOD解决方案

    class Shape
    {
        public:
            virtual void Draw() const = 0;
    };
    
    class Square : public Shape
    {
        public:
            virtual void Draw() const;
    };
         
    
    class Circle : public Shape
    {
        public:
            virtual void Draw() const;
    };
    
    void DrawAllShapes(vector<Shape*>& list)
    {
        vector<Shape*>::iterator i;
        for (i == list.begin(); i != list.end(); i++)
            (*i)->Draw();
    }

     

      可以看到,如果我们要扩展程序2.2.1.2-1中 DrawAllShapes函数的行为,使之能够绘制一种新的形状,我们只需增加一个新的Shape派生类。DrawAllShapes函数并不需要改 动。这样DrawAllShapes就符合了OCP。无需改动自身的代码就可以扩展它的行为。实际上,增加一个Triangle类对于这里展示的任何模块 完全没有影响。很明显,为了能够处理Triangle类,必须改动系统中的某些部分,但是这里展示的所有代码都无需改动。

     

      这个程序是符合OCP的。对它的改动是通过增加新代码进行的,而不是更改现有的代码。因此,它就不会引起像不遵循OCP的程序那样的连锁改动。所需要的改动仅仅是增加新的模块,以及为了能够实例化新类型的对象而进行的围绕main的改动。

    2.1.3 是的,我说谎了

      上面的例子其实并非是100%封闭的!如果我们要求所有的圆必须在正方形之前绘制,那么程序2.2.1.2-1中DrawAllShapes函数无法对这种变化做到封闭。

      这就导致一个麻烦的结果,一般而言,无论模块是多么的封闭,都会存在一些无法对之封闭的变化。没有对于所有的情况都贴切的模型。

      既然不可能完全封闭,那么就必须有策略地对待这个问题。也就是说,设计人员必须对于他设计的模块应该对哪种变化封闭作出选择。他必须先猜测出最有可能发生的变化种类,然后构造抽象来隔离那些变化。

     

       有句古老的谚语说:“愚弄我一次,应感羞愧的是你。再次愚弄我,应感羞愧的是我。”这也是一种有效的对待软件设计的态度。为了防止软件背负着不必要的复 杂性 ,我们会允许自己被愚弄一次。这意味着在我们最初编写代码时,假设变化不会发生。当变化发生时,我们就创建抽象来隔离以后发生的同类变化。简而言之,我们 愿意被第一颗子弹击中,然后我们会确保自己不再被同一支枪发射的其他任何子弹击中。

    2.2 结论

       在许多方面,OCP是面向对象设计的核心所在。遵循这个原则可以带来面向对象技术所声称的巨大好处(也就是:灵活性、可重用性以及可维护性)。然而,并 不是说只要使用一种面向对象语言就是遵循了这个原则。对于应用程序中的每个部分都肆意地进行抽象同样不是一个好主意。正确的做法是,开发人员应该仅仅对程 序中呈现出频繁变化的那些部分做出抽象。拒绝不成熟的抽象和抽象本身一样重要。


     

    3 Liskov替换原则(LSP)

    子类型(subtype)必须能够替换掉它们的基类型(base type)。

     

      Barbara Liskov首次写下这个原则是在1988年。她说道:

      这里需要如下替换性质:若对每个类型S的对象o1,都存在一个类型T的对象o2,使得在所有针对T编写的程序P中,o1替换o2后,程序P行为和功能不变,则S是T的子类型。

     

      想想违反该原则的后果,LSP的重要性就不言而喻了。假设有一个函数f,它的参数为指向某个基类型B的指针或引用。同样假设某个B的派生类D,如果把D的对象作为B类型传递给f,会导致f出现错误行为。那么D就违反了LSP。显然D对f来说是脆弱的。

     

       f的编写者会想去对D进行一些测试,以便于在把D的对象传递给f时,可以使f具有正确的行为。这个测试违反了OCP,因为此f对于B的所有不同的派生类 都不再是封闭的。这样的测试是一种代码的臭味,它是缺乏经验的开发人员(或者,更糟的,匆忙的开发人员)在违反了LSP时所产生的结果。

    3.1 正方形和矩形,微妙的违规

    程序2.3.1-1 Rectangle类和Square类

    class Rectangle
    {
        public:
            void SetWidth(double w) {itsWidth = w;}
            void SetHeight(double h) {itsHeight = h;}
            double GetWidth() {return itsWidth;}
            double GetHeight() {return itsHeight;}
        private:
            Point itsTopLeft;
            double itsWidth;
            double itsHeight;
    };
    
    class Square : public Rectangle
    {
        public:
            void SetWidth(double w) 
            {
                 Rectangle::SetWidth(w);
                 Rectangle::SetHeight(w);
            }
    
            void SetHeight(double h) 
            {
                 Rectangle::SetWidth(h);
                 Rectangle::SetHeight(h);
            }
    };

     

           从一般意义上讲一个正方形就是一个矩形。因此,把Square类视为从Rectangle类派生是合乎逻辑的。

           IS-A关系的这种用法有时被认为是面向对象分析(OOA)的基本技术之一。一个正方形是一个矩形,所以Square类就派生自Rectangle类。不 过这个想法会带来一些微妙但极为严重的问题。一般来说,这些问题是难以预见的,直到我们编写代码时才会发现它们。

     

      我们 首先注意到出问题的地方是,Square类并不同时需要成员变量itsHeight和itsWidth。但是Square类仍会在Rectangle类中 继承它们。显然这是个浪费。在许多情况下,这种浪费是无关紧要的。但是,如果我们必须创建成百上千个Square对象,浪费的程度则是巨大的。

     

      假设目前我们并不十分关心内存效率。从Rectangle类派生Square类也会产生其他一些问题。请考虑下面这个函数:

    void f (Rectangle& r)
    {
        r.SetWidth(32);    // Calls Rectangle::SetWideth()
    }

       如果我们向这个函数传递一个指向Square对象的引用,这个Square对象就会被破坏,因为他们的长并不会改变。这显然违反了LSP。以 Rectangle派生类的对象作为参数传入是,函数f不能正确运行。错误的原因是在Rectangle中没有把SetWidth和SetHeight声 明为虚函数,因此它们不是多态的。

     

      这个错误很容易修正。然而,如果派生类的创建会导致我们改变基类,这就常常意味着设 计是有缺陷的。当然也违反了OCP。也许有人会反驳说,真正的设计缺陷是忘记把SetWidth和SetHeight声明为虚函数,而我们已经作了修正。 可是,这很难让人信服,因为设置一个长方形的长和宽是非常基本的操作。如果不是预见到Square类的存在,我们凭什么要把这两个函数声明为虚函数呢?

     

      尽管如此,假设我们接受这个理由并修正这些类。

     

    程序2.3.1-2 修正后的Rectangle类

    class Rectangle
    {
        public:
            virtual void SetWidth(double w) {itsWidth = w;}
            virtual void SetHeight(double h) {itsHeight = h;}
            double GetWidth() {return itsWidth;}
            double GetHeight() {return itsHeight;}
        private:
            Point itsTopLeft;
            Double itsWidth;
            Double itsHeight;
    };

     

    3.1.1 真正的问题

       现在Square和Rectangle看起来都能够正常工作。无论Square对象进行什么样的操作,它都和数学意义上的正方形保持一致。无论 Rectangle对象进行什么样的操作,它都和数学意义上的长方形保持一致。此外,可以向接受指向Rectangle的指针或引用的函数传递 Square,而Square依然保持正方形的特性,与数学意义上的正方形一致。

     

      这样看来,设计似乎是自相容的、正确的。可是,这个结论是错误的。一个自相容的设计未必就和所有的用户程序自相容。考虑下面的函数g:

     

    void g (Rectangle& r)
    {
        r.SetWidth(5);
        r.Setheight(4);
        assert(r.Area() == 20);
    }

     

       这个函数认为所传递进来的一定是Rectangle,并调用了其成员函数SetWidth和SetHeight。对于Rectangle来说,此函数运 行正确,但如果传递进来的是Square对象就发生断言错误(assertion error)。所以,真正的问题是:函数g的编写者假设改变Rectangle的宽不会导致其长的改变。

     

      很显然,改变 一个长方形的宽不会影响它的长的假设是合理的!然而,并不是所有可以作为Rectangle传递的对象都满足这个假设。如果把一个Square类的实例传 递给g这样做了假设的函数,那么这个函数就会出现错误的行为。函数g对于Square/Rectangle层次结构来说是脆弱的。

     

      函数g的表现说明有一些使用指向Rectangle对象的指针或者引用的函数,不能正确地操作Square对象。对于这些函数来说,Square不能替换Rectangle,因此Square和Rectangle之间的关系是违反LSP的。

    3.1.2 IS-A是关于行为的

      那么究竟是怎么会使?Square和Rectangle这个显然合理的模型为什么会有问题呢?毕竟,Square应该就是Rectangle。难道他们之间不存在IS-A关系吗?

     

       对于那些不是g的编写这而言,正方形可以是长方形,但是从g的角度来看,Square对象绝对不是Rectangle对象。为什么!?因为Square 对象的行为方式和函数g所期望的Rectangle对象的行为方式不相容。从行为方式的角度来看,Square不是Rectangle,对象的行为方式才 是软件真正所关注的问题。LSP清楚地指出,OOD中IS-A关系是就行为方式而言的,行为方式是可以进行合理假设的,是客户程序所依赖的。

    3.2 从派生类中抛出异常

      另一种LSP的违规形式是在派生类的方法中添加了其他基类不会抛出的异常。如果基类的使用者不期望这些异常,那么把它们添加到派生类的方法中就会导致不可替换性。此时要遵循LSP,要么就必须改变使用者的期望,要么派生类就不应该抛出这些异常。

    3.3 有效性并非本质属性

      在考虑一个特定设计是否恰当时,不能完全孤立地来看这个解决方案。必须要根据设计的使用者做出的合理假设来审视它。

      有谁知道设计的使用者会做出什么样的合理假设呢?大多数这样的假设都很难预测。事实上,如果试图去预测所有这些假设,我们所得到的系统很可能会充满不必要的复杂性的臭味。因此,像所有其他原则一样,通常最好的方法只预测那些最明显的对于LSP的违反情况而推迟所有其他的预测,知道出现相关的脆弱性的臭味时,才去处理它们。

    3.4 结论

      OCP是OOD中很多说法的核心。如果这个原则应用得有效,应用程序就会具有更多的可维护性、可重用性以及健壮性。LSP是使OCP成为可能的主要原则之一。正是子类型的可替换性才使得使用基类类型的模块在无需修改的情况下就可以扩展。这种可替换性必须使开发人员可以隐式依赖的东西。因此,如果没有显式地强制基类类型的契约,那么代码就必须良好地并且明显地表达出这一点。

     

      俗语“IS-A”的含义过于宽泛以至于不能作为子类型的定义。子类型的正确定义是“可替换性的”,这里的可替换性可以通过显式或隐式的契约来定义。


     

    4 依赖倒置原则(DIP)

    A、 高层模块不应该依赖于低层模块,两者都应该依赖于抽象。

    B、  抽象不应该依赖于细节,细节应该依赖于抽象。               

     

       这条原则的名字中使用“倒置”这个词,是由于许多传统的软件开发方法,例如结构化分析和设计,总是倾向于创建一些高层模块依赖于低层模块,策略 (policy)依赖于细节的软件结构。实际上这些方法的目的之一就是要定义子程序层次结构,该层次结构描述了高层模块怎样调用低层模块。第一章中1.2 节的Copy程序的初始设计就是这种层次结构的一个典型示例。一个设计良好的面向对象的程序,其依赖程序结构相对于传统的过程式方法设计的通常结构而言就 是被“倒置”了。

     

      请考虑一下高层模块依赖于低层模块时意味着什么。高层模块包含了一个应用程序的重要的策略选择和业务 模型。正是这些高层模块才使得其所在的应用程序区别于其他。然而,如果这些高层模块依赖于低层模块,那么对低层模块的改动就会直接影响到高层模块,从而迫 使它们依次做出改动。

     

      这种情形是非常荒谬的!本应该是高层的策略设置模块去影响低层的细节实现模块的。包含业务规则的模块应该优先于并独立于包含实现细节的模块。无论如何高层模块都不应该依赖于低层模块。

     

       此外,我们更希望能够重用的是高层的策略设置模块。我们已经非常擅长于通过子程序库的形式来重用低层模块。如果高层模块依赖于低层模块,那么在不同的上 下文中重用高层模块就会变得非常困难。然而,如果高层模块独立于低层模块,那么高层模块就可以非常容易的被重用。该原则是框架(framework)设计 的核心原则。

    4.1 层次化

    请看图2.4.1-1的层次化方案:

     图2.4.1-1 简单的层次化方案

     

       图中,高层的Policy Layer使用了低层的Mechanism Layer,而Mechanism Layer又使用了更细节的层Utility Layer。这看起来似乎是正确的,然而它存在一个隐伏的错误特征,那就是:Policy Layer对于其下一直到Utility Layer的改动都是敏感的。这种依赖关系是传递的。Policy Layer依赖于某些依赖于Utility Layer的层次;因此Policy Layer传递性的依赖于Utility Layer。这是非常糟糕的。

     

      图 2.4.1-2展示了一个更为适合的模型。每个较高层次都为它所需的服务声明一个抽象接口,较低的层次实现了这个抽象接口,每个高层类都通过该抽象接口使 用下一层,这样,高层就不依赖于低层。低层反而依赖于在高层中声明的抽象服务接口。这不仅解除了Policy Layer对于Utility Layer的传递依赖关系,甚至也解除了Policy Layer对Mechanism Layer的依赖关系。

    图2.4.1-2 倒置的层次

     

      请注意这里的倒置不仅仅是依赖关系的倒置,它也是接口所有权的倒置。我们通常会认为工具库应该拥有它们自己的接口。但是当应用了DIP时,我们发现,往往是客户端拥有抽象接口,而它们的服务者这从这些抽象接口派生。

    4.1.1 倒置接口所有权

      这就是著名的Hollywood原则:“Don’t call us, we’ll call you.”(不要调用我们,我们会调用你。)低层模块实现了在高层模块中声明并被高层模块调用的接口

     

       通过倒置接口所有权,对于Mechanism Layer或者Utility Layer的任何改动都不会在影响到Policy Layer。而且,Policy Layer可以在实现了Policy Service Interface的任何上下文中重用。这样,通过倒置这些依赖关系,我们创建了一个更灵活、更持久、更易改变的结构。

    4.1.2 依赖于抽象

      一个稍微简单但仍然非常有效的对于DIP的解释,是这样一个简单的启发式规则:“依赖于抽象 ”。这是一个简单的陈述,该启发式规则建议不应该依赖于具体类——也就是说,程序中所有的依赖关系都应该终止于抽象类或者接口。

     

    根据启发式规则:

    • 任何变量都不应该持有一个指向具体类的指针或者引用
    • 任何类都不应该从具体类派生
    • 任何方法都不应该覆写它的任何基类中已经实现了的方法

     

       当然,每个程序都会有违反该规则的情况。有时必须要创建具体类的实例,而创建这些实例的模块将会依赖于它们。此外,该启发规则对于那些虽然是具体但却稳定(nonvolatile)的类来说似乎不太合理。如果一个具体类不太会改变,并且也不会创建其他类似的派生类,那么依赖于它并不会造成损害。

     

      例如,在大多数系统中,描述字符串的类都是具体的(如Java中的String类),而该类有时稳定的,也就是说,它不太会改变。因此,直接依赖于它不会造成损害。

      然而,我们在应用程序中所编写的大多数具体类都是不稳定的。我们不想直接依赖于这些不稳定的具体类。通过把它们隐藏在抽象接口的后面,可以隔离它们的不稳定性。

      这不是一个完美的解决方案。常常,如果不稳定类的接口必须变化时,这个变化一定会影响到该类的抽象接口。这种变化破坏了抽象接口维系的隔离性。

      由此可知,该启发规则对问题的考虑有点简单了。另一方面,如果看得远一点,认为是由客户来声明它需要的服务接口,那么仅当客户需要时才会对接口进行改变。这样,改变实现抽象接口的类就不会影响到客户。

    4.2 结论

      使用传统的过程化程序设计所创建出来的依赖关系结构,策略是依赖于细节的。这是糟糕的,因为这样会使策略受到细节改变的影响。面向对象的程序设计倒置了依赖关系结构,使得细节和策略都依赖于抽象,并且常常是客户拥有服务接口。

     

      事实上,这种依赖关系的倒置正好是面向对象设计的标志所在。使用何种语言来编写程序是无关紧要的。如果程序的依赖关系是倒置的,它就是面向对象的设计。否则,它就是过程化的设计。

     

      DIP是实现许多面向对象技术所宣称的好处的基本低层机制。它的正确应用对于实现可重用的框架来说是必须的。同时它对构建在变化面前富有弹性的代码也是非常重要的。由于抽象和细节彼此隔离,所以代码也非常容易维护。


     

    5 接口隔离原则(ISP)

    不应该强迫客户依赖于它们不要的方法。接口属于客户,不属于它所在的类层次结构。

     

      这个原则用来处理“胖”接口所具有的缺点。如果类的接口不是内聚的(cohesive),

      就表示该类具有“胖”接口。换句话说,类的“胖”接口可以分解成多组方法。每一组方法都服务于一组不同的客户程序。这样,一些客户程序可以使用一组成员函数,而其他客户程序可以使用其他组的成员函数。

     

      ISP承认存在有一些对象,它们确实不需要内聚的接口:但是ISP建议客户程序不应该看到它们作为单一的类存在。相反,客户程序看到的应该是多个具有内聚接口的抽象基类。

     

       如果强迫客户程序依赖于那些它们不使用的方法,那么这些客户程序就面临着由于这些没使用的方法的改变所带来的变更。这无意中导致了所有客户程序之间的耦 合。换种说法,如果一个客户程序依赖于一个含有它不使用的方法的类,但是其他客户程序却要使用该方法,那么当其他客户要求这个类改变时,就会影响到这个客 户程序。我们希望尽可能地避免这种耦合,因此我们希望分离接口。

    5.1 ATM用户界面的例子

       现在我们考虑一下这样一个例子:传统的自动取款机(ATM)问题。ATM需要一个非常灵活的用户界面。它的输出信息需要被转换成许多不同的语言。输出信 息可能被显示在屏幕上,或者布莱叶盲文书写板上,或者通过语音合成器说出来。显然,通过创建一个抽象基类,其中具有用来处理所有不同的、需要被该界面呈现 的消息的抽象方法,就可以实现这种需求。如图2.5.1-1所示:

     

    图2.5.1-1 ATM界面层次结构

     

       同样,可以把每个ATM可以执行的不同操作封装为类Transaction的派生类。这样,我们可以得到类DepositTransaction、 WithdrawalTransaction以及TransferTransaction。每个类都调用UI的方法。例如,为了要求用户输入希望存储的金 额,DepositTransaction对象会调用UI类中的RequestDepositAmount方法。同样,为了要求用户输入想要转帐的金 额,TransferTransaction对象会调用UI类中的RequestTransferAmount方法。图2.5.1-2为相应的类图。

     

    图2.5.1-2 ATM操作层次结构

     

       请注意,这正好是ISP告诉我们应该避免的情形。每个操作所使用的UI的方法,其他的操作类都不会使用。这样,对于任何一个Transaction的派 生类的改动都会迫使对UI的相应改动,从而也影响了其他所有Transaction的派生类以及其他所有依赖于UI接口的类。这样的设计就具有了僵化性以 及脆弱性的臭味。

     

      例如,如果要增加一种操作PayGasBillTransaction,为了处理该操作想要显示的特 定消息,就必须在UI中加入新的方法,糟糕的是,由于DepositTransaction、WithdrawalTransaction以及 TransferTransaction全部都依赖于UI接口,所以它们都需要重新编译。更糟糕的是,如果这些操作都作为不同的DLL或者共享库部署的 话,那么这些组件必须得重新部署,即使它们的逻辑没有做过任何改动。你闻到粘滞性的臭味了吗?

     

      通过将UI接口分解成像DepositUI、WithdrawalUI以及TransferUI这样的单独接口,可以避免这种不合适的耦合。最终的UI接口可以去多重继承这些单独的接口。图2.5.1-3展示了这个模型。

    图2.5.1-3 分离的ATM UI接口

     

       每次创建一个Transaction类的新派生类时,抽象接口UI就需要增加一个相应的基类并且因此UI接口以及所有他的派生类都必须改变。不过,这些 类并没有被广泛使用。事实上,它们可能仅被main或者那些启动系统并创建具体UI实例之类的过程所使用。因此,增加新的UI基类所带来的影响被减至最 小。

    5.2 结论

      胖类(fat class)会导致它们的客户程序之间产生不正常的并且有害的耦合关系。当一个客户程序要求该胖类进行一个改动时,会影响到所有其他的客户程序。因此,客 户程序应该仅仅依赖于它们实际调用的方法。通过把胖类的接口分解成多个特定于客户程序的接口,可以实现这个目标。每个特定于客户程序的接口仅仅声明它的特 定客户或者客户组调用的那些函数。接着,该胖类就可以继承所有特定于客户程序的接口,并实现它们。这就解决了客户程序和它们没有调用的方法间的依赖关系, 并使客户程序之间互不依赖。

     

    展开全文
  • 1、开放-封闭原则(OCP:Open-Closed Principle)...通俗地说就是,开发一个软件时,应该可以对它进行扩展(开放),而在进行这些扩展的时候,不需要对原来的程序进行修改(关闭)。   目前,对于OCP的实现,主要就
    1、开放-封闭原则(OCP:Open-Closed Principle)Software
     entities should be open for extension, but closed for modification.通俗地说就是,开发一个软件时,应该可以对它进行扩展(开放),而在进行这些扩展的时候,不需要对原来的程序进行修改(关闭)。 
           目前对于OCP的实现,主要就是抽象。把系统所有可能的行为抽象成一个抽象底层,这个抽象底层规定出所有的具体类必须提供方法的特征,这个抽象层要预见所有可能的扩展,从而使得在任何扩展情况下,系统的抽象层不需修改,同时由于可以从抽象层导出一个或多个新的具体类来改变系统的行为,因此对于可变的部分,系统设计对扩展是开放的。
           对于OCP,在工程上被描述成“可变性封装原则”,就是把系统的可变性封装起来,这就意味着:
           ①一种可变性不应该散落在软件的各个角落,而应该把它封装成一个对象;
           ② 一种可变性不应该和别的可变性混淆在一起。

    2、单一职责原则(SRP:Single Responsibility Principle) 对一个类而言,应该仅有一个引起它变化的原因。如果有多于一个的动机去改变一个类,那么这个类就具有多于一个的指责,应该把多余的职责分离出去,分别再创建一些类来完成每一个职责。单一职责原则是实现高内聚低耦合的最好办法。
           如果变化总是引起两个或多个职责同时发生变化,这时就应该将这些职责放到同一个类中。
           对于变化的封装,应该遵循以下原则:
           ①一个合理的类,应该仅有一个引起它变化的原因,即单一职责;
           ②在没有变化征兆的情况下应用SRP或者其他原则是不明智的;
           ③在需求实际发生变化时就应该应用SRP等原则来重构代码;
           ④利用测试驱动开发会迫使我们在设计出现问题前分离不合理的代码;
           ⑤如果测试不能迫使职责分离,僵化性和脆弱性的问题就会变得很突出,那就应该用外观(FaCade)或代理(Proxy)模式对代码重构。

    3、里氏代换原则(LSP:Liskov Substitution Principle)继承必须确保超类所拥有的性质在子类中依然成立,也就是说,当一个子类的实例应该能够替换任何其超类的实例时,它们之间才具有继承的关系。LSP表述的是在同一个继承体系中的对象应该有共同的行为特征(状态&行为)。
           1988年,B.Meyer提出了契约式设计理论,从形式化方法中借鉴了一套确保对象行为和自身状态的方法,其基本概念很简单:
            ①每个方法调用之前,该方法应该校验传入参数的正确性,只有正确才能执行该方法,否则认为调用方违反契约,不予执行。这称为前置条件
            ②一旦通过前置条件的检验,方法必须执行,并且必须确保执行结果符合契约,这称之为后置条件
            ③对象本身有一套对自身状态进行校验的检查条件,以确保该对象的本质不发生改变,这称之为不变式
            以上为单个对象的约束条件。为了满足LSP,当存在继承关系时,子类中的方法的前置条件必须与超类中被覆盖的方法的前置条件相同或者更宽松;而子类中方法的后置条件必须与超类中被覆盖的方法的后置条件相同或者更为严格。

    4、依赖倒置原则(DIP:Dependence Inversion Principle): 
    如果一个类的一个成员或参数为一个具体类型,那么这个类就依赖于那个具体类型;如果在一个继承结构中,上层类中的一个成员或参数为一个下层类型,那么这个继承结构就是高层依赖于底层了,则需尽量面向接口或者抽象编程,即在高层和底层之间引入接口或者抽象类,让高层和底层同时依赖于抽象。
           抽象不应该依赖于细节,细节应该依赖于抽象。一些辅助原则可以帮助我们更好地运用依赖倒置原则:
           ①任何变量都不应该持有一个指向具体类的引用;
           ②任何类都不应该从具体类派生;
           ③任何方法都不应该覆盖它的任何基类中已经实现了的方法。 
           抽象反映高层策略,就是应用中那些不会随着具体细节的改变而改变的规则,常用的词语就是隐喻。仔细分析需求,先找出那些业务规则,然后把它们抽象出来形成接口。层次化的设计 ,常见的方法就是划分出显示层、业务层及持久层,再在每层做抽象。这是最粗糙的层次化,可以在每层再根据需要划分更细的层次。在实现的时候始终遵循前面提到的原则:只依赖于接口。

    5、接口隔离原则(ISP:Interface Segregation Principle)接口尽量细化,同时接口中的方法尽量少。一个接口中包含太多行为时,容易导致它们的客户程序之间产生不正常的依赖关系。
           接口隔离原则与单一职责的定义规则是不同的,单一职责要求的是类和接口职责单一,注重的是职责,没有要求接口的方法减少。接口隔离原则要求“尽量使用多个专门的接口”。专门的接口是指提供给多个模块的接口,拥有几个模块就应该提供几个接口,而不是建立一个庞大的臃肿的接口让所有的模块都可以来访问。
            接口要高内聚,就是提高接口、类、模块的处理能力,减少对外的交互,在接口中尽量少公布public方法,只提供访问者需要的方法。在实践应用时可以根据这样的规则来衡量:一个接口只服务于一个子模块或者业务逻辑。

    6、迪米特法则(LoD:Law of Demeter)每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。迪米特法则不希望类之间建立直接的接触,如果真的有需要建立联系,也希望能通过它的友元类来转达。因此,应用迪米特法则可能造成系统中存在大量的中介类,这些类之所以存在完全是为了传递类之间的相互调用关系。
           为了克服狭义迪米特法则的缺点,可以使用依赖倒置原则。
           在将迪米特原则运用到系统的设计中时,需要注意以下几点:
           ① 在类的划分上,应当创建弱耦合的类,类之间的耦合越弱,就越有利于复用;
           ②在类的结构设计上,每一个类都应当尽量降低成员的访问权限;
           ③在类的设计上,只要可能,一个类应当设计成不变类;
           ④在对其他类的引用上,一个对象对其他对象的引用应降到最低;
           ⑤尽量限制局部变量的有效范围。 

    7、其他原则
            ①重用发布等价原则(REP):重用的粒度就是发布的粒度。
            ②共同重用原则(CCP):一个包中的所有类应该是共同重用的,如果重用了包中的一个类,那么就要重用包中的所有类,相互之间没有紧密联系的类不应该在同一个包中。
            ③共同封闭原则(CRP):包中所有类对于同一类性质的变化应该是共同封闭的。一个变化若对一个包影响,则将对包中的所有类产生影响,而对其他包不造成任何影响。
            ④无依赖原则(ADP):在包的依赖关系中不允许存在环,细节不应该被依赖。
            ⑤稳定依赖原则(SDP):朝着稳定的方向进行依赖,应该把封装系统高层设计的软件放进稳定的包中,不稳定的包中应该只包含那些很可能会改变的软件。
            ⑥稳定抽象原则(SAP):包的抽象成都应该和其他稳定程度一致。一个稳定的包应该是抽象的,一个不稳定的包也应该是抽象的。
            ⑦缺省抽象原则(DAP):在接口和实现接口的类之间引入一个抽象类,这个类实现了接口的大部分操作。
            ⑧接口设计原则(IDP):规划一个接口而不是实现一个接口。
            ⑨黑盒原则(BBP):多用类的组合,少用类的继承。
            ⑩不要构造具体的超类原则(DCSP):避免维护具体的超类。 
    展开全文
  • 面向对象软件设计说明书模板

    千次阅读 2007-08-08 10:50:00
    面向对象软件设计说明书模板blueski推荐 [2007-7-31]出处:http://www.sawin.cn/doc/Document/DocPattern/blueski1402.htm作者:不详 1 概述 1.1 系统简述 对系统要完成什么,所面向的用户以及系统运行的环境的...
  • 面向对象软件设计的“开—闭”原则   1.什么是开闭原则   “开—闭”原则是指软件实体应当对扩展性开放,对修改关闭。即软件实体应该在不修改的前提下扩展,这个原则实际上为软件设计指明了...
  • 通过实施角色扮演游戏的课程来演示 目录 介绍 对象类型 1.实体对象 2.控制对象 ...关键设计原则 ...大多数现代编程语言都支持并鼓励面向对象编程(OOP)。虽然最近我们似乎看到了一点点偏离...
  • what: 软件设计过程中对扩展性开放,对修改关闭。即软件实体应该在不修改的前提下扩展,这个原则实际上为软件设计指明了目标。 我们知道软件设计应当充分考虑软件的可维护性,即需求...在面向对象设计中,不允许更改
  • 可行性研究报告+项目开发计划+需求规格说明书+概要设计说明书+详细设计说明书+用户操作手册+测试计划+面向对象软件设计说明书模板+……
  • 正如牛顿三大定律在经典力学中的位置一样,“开-闭”原则(Open-Closed Principle)是面向对象的可复用设计(Object Oriented Design或OOD)的基石。其他设计原则(里氏代换原则、依赖倒转原则、合成/聚合复用...
  • 我们如何知道软件设计的优劣呢?以下是一些拙劣设计的症状,当软件出现下面任何一种气味时,就表明软件正在腐化。 僵化性(Rigidity):很难对系统进行改动,因为每个改动都会迫使许多对系统其他部分的其他改动。...
  •  1.2 软件设计目标 这部分论述整个系统的设计目标,明确地说明哪些功能是系统决定实现而哪些时不准备实现的。同时,对于非功能性的需求例如性能、可用性等,亦需提及。需求规格说明书对于这部分的内容来说是很重要...
  • The Open-Closed Principle (OCP) - 面向对象软件设计的开闭原则全文:http://www.lifevv.com/sysdesign/doc/20071117113059713.html
  • 3.1 软件设计的目标在设计时首要考虑的不适形象进度,而是系统的可维护性:(1)可扩展性(2)可修改性(3)可替换性 3.2 “开-闭”原则“开-闭”原则指软件实体应当对扩展开放,对修改关闭。即软件实体应该在不修改...
  • 面向对象软件设计”这个术语及其相关话题对于很多开发人员来说已经是耳熟能详了,甚至听腻了。但是,对不住各位,为了吸引眼球和引起“异性”注意,本座还是落入俗套选择这个热门话题作为本博客的开张炒作篇目。请...
  • 1.2 软件设计目标 这部分论述整个系统的设计目标,明确地说明哪些功能是系统决定实现而哪些时不准备实现的。同时,对于非功能性的需求例如性能、可用性等,亦需提及。需求规格说明书对于这部分的内容来说是很重要的...
  • 所有对象之间的关联必须被确定并且必须指明联系的基数(一对一、一对多还是多对多,0..1,*,1..*)。聚合和继承关系必须清楚地确定下来。每个图必须附有简单的说明。 可能经过多次反复之后才能得到系统的正确的对象...
  • 场景名:给它一个可以望文生义的名字 场景描述:简要叙述场景是干什么的以及发生的动作的顺序。 顺序图:描述各种事件及事件...可能你想为每个对象画一个状态图,但事实上会导致太多不期望的细节信息,只需要确定系统中
  • 4.2 系统结构设计 这部分要求提供高层系统结构的描述,使用方框图来显示主要的组件及组件间的交互。最好是把逻辑结构同物理结构分离,对前者进行描述。别忘了说明图中用到的俗语和符号。 4.2.1 顶层系统结构 4.2.2 ...
  • 即软件实体应该在不修改的前提下扩展,这个原则实际上为软件设计指明了目标。我们知道软件设计应当充分考虑软件的可维护性,即需求发生变化的时候软件结构能够灵活地适应这种变化。就评价软件的可维护性而言,“开—...
  • 类型: 描述: 约束: 2. 属性:属性2 6.1.1.2 方法描述: 1. 方法:方法1 返回类型: 参数: 返回值: Pre-Condition: Post-Condition: 读取/修改的属性: 调用的方法: 处理逻辑: 测试例:用什么参数调用该...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 26,916
精华内容 10,766
关键字:

面向对象软件设计