分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow
我们在应用程序开发中,一般要求尽量两做到可维护性和可复用性。
应用程序的复用可以提高应用程序的开发效率和质量,节约开发成本,恰当的复用还可以改善系统的可维护性。而在面向对象的设计里面,可维护性复用都是以面向对象设计原则为基础的,这些设计原则首先都是复用的原则,遵循这些设计原则可以有效地提高系统的复用性,同时提高系统的可维护性。 面向对象设计原则和设计模式也是对系统进行合理重构的指导方针。
常用的面向对象设计原则包括7个,这些原则并不是孤立存在的,它们相互依赖,相互补充。
1.单一职责原则(Single Responsibility Principle,SRP):类的职责要单一,不能将太多的职责放在一个类中。(高内聚、低耦合)
定义:一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中。(Every object should have a single responsibility, and that responsibility should be entirely encapsulated by the class.),即又定义有且仅有一个原因使类变更。
原则分析: 1)一个类(或者大到模块,小到方法)承担的职责越多,它被复用的可能性越小,而且如果一个类承担的职责过多,就相当于将这些职责耦合在一起,当其中一个职责变化时,可能会影响其他职责的运作。
2)类的职责主要包括两个方面:数据职责和行为职责,数据职责通过其属性来体现,而行为职责通过其方法来体现。
3)单一职责原则是 实现高内聚、低耦合的 指导方针,在很多代码重构手法中都能找到它的存在,它是最简单但又最难运用的原则,需要设计人员发现类的不同职责并将其分离,而发现类的多重职责需要设计人员具有较强的分析设计能力和相关重构经验。 例子:
最简单例子是:一个数据结构职责类和算法行为都放在一个类User。我们应该把数据结构和行为分开。现使用单一职责原则对User类进行重构。2.开闭原则( Open – ClosedPrinciple ,OCP ):对扩展开放,对修改关闭(设计模式的核心原则是)
定义:一个软件实体(如类、模块和函数)应该对扩展开放,对修改关闭. 意思是,在一个系统或者模块中,对于扩展是开放的,对于修改是关闭的,一个 好的系统是在不修改源代码的情况下,可以扩展你的功能. 而实现开闭原则的关键就是抽象化.
原则分析 :
1)当软件实体因需求要变化时, 尽量通过扩展已有软件实体,可以提供新的行为,以满足对软件的新的需求,而不是修改已有的代码,使变化中的软件有一定的适应性和灵活性 。已有软件模块,特别是最重要的抽象层模块不能再修改,这使变化中的软件系统有一定的稳定性和延续性。
2)实现开闭原则的关键就是抽象化 :在”开-闭”原则中,不允许修改的是抽象的类或者接口,允许扩展的是具体的实现类,抽象类和接口在”开-闭”原则中扮演着极其重要的角色..即要预知可能变化的需求.又预见所有可能已知的扩展..所以在这里”抽象化”是关键!
3)可变性的封闭原则:找到系统的可变因素,将它封装起来. 这是对”开-闭”原则最好的实现. 不要把你的可变因素放在多个类中,或者散落在程序的各个角落. 你应该将可变的因素,封套起来..并且切忌不要把所用的可变因素封套在一起. 最好的解决办法是,分块封套你的可变因素!避免超大类,超长类,超长方法的出现!!给你的程序增加艺术气息,将程序艺术化是我们的目标!
例子:我们前面提到的模板方法模式和观察者模式都是开闭原则的极好体现。
3.里氏代换原则( Liskov Substitution Principle ,LSP ):任何基类可以出现的地方,子类也可以出现
定义:第一种定义方式相对严格:如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有变化,那么类型S是类型T的子类型。
第二种更容易理解的定义方式: 所有引用基类(父类)的地方必须能透明地使用其子类的对象。即 子类能够必须能够替换基类能够从出现的地方。子类也能在基类 的基础上新增行为。 ( 里氏代换原则由 2008 年图灵奖得主、美国第一位计算机科学女博士、麻省理工学院教授 Barbara Liskov 和卡内基 . 梅隆大学 Jeannette Wing 教授于 1994 年提出。其原文如下: Let q(x) be a property provableabout objects x of type T. Then q(y) should be true for objects y of type Swhere S is a subtype of T. ) 原则分析: 1)讲的是基类和子类的关系,只有这种关系存在时,里氏代换原则才存在。正方形是长方形是理解里氏代换原则的经典例子。 2)里氏代换原则可以通俗表述为:在 软件中如果能够使用基类对象,那么一定能够使用其子类对象 。把基类都替换成它的子类,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类的话,那么它不一定能够使用基类。 3) 里氏代换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此 在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象 。
例子:正方形不是长方形 在数学领域里,正方形毫无疑问是长方形,它是一个长宽相等的长方形。所以,我们开发的一个与几何图形相关的软件系统中,让正方形继承自长方形是顺利成章的事情。由于正方形的度和宽度必须相等,所以在方法setLength和setWidth中,对长度和宽度赋值相同。
正方形集成长方形:
由于正方形的度和宽度必须相等,所以在方法setLength和setWidth中,对长度和宽度赋值相同。类TestRectangle是我们的软件系统中的一个组件,它有一个resize方法要用到基类Rectangle,resize方法的功能是模拟长方形宽度逐步增长的效果 :
测试类TestRectangle:
我们运行一下这段代码就会发现,假如我们把一个普通长方形作为参数传入resize方法,就会看到长方形宽度逐渐增长的效果,当宽度大于长度,代码就会停止,这种行为的结果符合我们的预期;假如我们再把一个正方形作为参数传入resize方法后,就会看到正方形的宽度和长度都在不断增长,代码会一直运行下去,直至系统产生溢出错误。所以,普通的长方形是适合这段代码的,正方形不适合。
我们得出结论:在resize方法中,Rectangle类型的参数是不能被Square类型的参数所代替,如果进行了替换就得不到预期结果。因此,Square类和Rectangle类之间的继承关系违反了里氏代换原则,它们之间的继承关系不成立,正方形不是长方形。
优缺点: 在面向对象的语言中,继承是必不可少的、非常优秀的语言机制,它有如下优点: 1)代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性; 2)提高代码的重用性;
3)子类可以形似父类,但又异于父类,“龙生龙,凤生凤,老鼠生来会打洞”是说子拥有父的“种”,“世界上没有两片完全相同的叶子”是指明子与父的不同;
4)提高代码的可扩展性,实现父类的方法就可以“为所欲为”了,君不见很多开源框架的扩展接口都是通过继承父类来完成的;
5)提高产品或项目的开放性。
自然界的所有事物都是优点和缺点并存的,即使是鸡蛋,有时候也能挑出骨头来,继承的缺点如下:
1)继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法;
2)降低代码的灵活性。子类必须拥有父类的属性和方法,让子类自由的世界中多了些约束;
3)增强了耦合性。当父类的常量、变量和方法被修改时,必需要考虑子类的修改,而且在缺乏规范的环境下,这种修改可能带来非常糟糕的结果——大片的代码需要重构。
4.依赖倒转原则( Dependence Inversion Principle ,DIP ):要依赖抽象,而不要依赖具体的实现.
定义:高层模块不应该依赖低层模块,它们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象。简单的说,依赖倒置原则要求客户端依赖于抽象耦合。原则表述:
1)抽象不应当依赖于细节;细节应当依赖于抽象;
2)要针对接口编程,不针对实现编程。
原则分析:
1)如果说开闭原则是面向对象设计的目标,依赖倒转原则是到达面向设计”开闭”原则的手段..如果要达到最好的”开闭”原则,就要尽量的遵守依赖倒转原则. 可以说依赖倒转原则是对”抽象化”的最好规范! 我个人感觉,依赖倒转原则也是里氏代换原则的补充..你理解了里氏代换原则,再来理解依赖倒转原则应该是很容易的。
2)依赖倒转原则的常用实现方式之一是在代码中使用抽象类,而将具体类放在配置文件中。
3) 类之间的耦合: 零耦合 关系, 具体耦合 关系, 抽象耦合 关系。 依赖倒转原则要求客户端依赖于抽象耦合 ,以抽象方式耦合是依赖倒转原则的关键。
理解这个依赖倒置,首先我们需要明白依赖在面向对象设计的概念: 依赖关系(Dependency): 是一种 使用关系 ,特定事物的改变有可能会影响到使用该事物的其他事物,在需要表示一个事物使用另一个事物时使用依赖关系。( 假设 A 类的变化引起了 B 类的变化,则说名 B 类依赖于 A 类。 )大多数情况下, 依赖关系体现在某个类的方法使用另一个类的对象作为参数 。 在UML中,依赖关系用带箭头的虚线表示, 由依赖的一方指向被依赖的一方。由于需求的变化,该系统可能需要增加新的数据源或者新的文件格式,每增加一个新的类型的数据源或者新的类型的文件格式,客户类 MainClass 都需要修改源代码,以便使用新的类,但违背了开闭原则。现使用依赖倒转原则对其进行重构。
- <code class=“language-java”>/**
- * 抽象接口
- * @author mo-87
- *
- */
- abstract public class AbstractSource {
- abstract public void getSource();
- }
- /**
- * 抽象接口
- * @author mo-87
- *
- */
- abstract public class AbstractStransformer {
- abstract public void transform();
- }
- /**
- * 具体实现
- * @author mo-87
- *
- */
- public class DatabaseSource extends AbstractSource{
- public void getSource(){
- System.out.println(“Get source data”);
- }
- }
- /**
- * 依赖注入是依赖AbstractSource抽象注入的,而不是具体
- * DatabaseSource
- *
- * @author mo-87
- *
- */
- public class XMLStransformer {
- /**
- *
- */
- private AbstractSource source;
- /**
- * 构造注入(Constructor Injection):通过构造函数注入实例变量。
- */
- public void XMLStransformer(AbstractSource source){
- this.source = source;
- }
- /**
- * 设值注入(Setter Injection):通过Setter方法注入实例变量。
- * @param source : the sourceto set
- */
- public void setSource(AbstractSource source) {
- this.source = source;
- }
- /**
- * 接口注入(Interface Injection):通过接口方法注入实例变量。
- * @param source
- */
- public void transform(AbstractSource source ) {
- source.getSource();
- System.out.println(“Stransforming …”);
- }
- }</code>
依赖注入的三种写法: 造注入(Constructor Injection):通过构造函数注入实例变量。 值注入(Setter Injection):通过Setter方法注入实例变量。 口注入(Interface Injection):通过接口方法注入实例变量。
优点:采用依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性。
依赖正置就是类间的依赖是实实在在的实现类间的依赖,也就是面向实现编程,这也是正常人的思维方式,我要开奔驰车就依赖奔驰车,我要使用笔记本电脑就直接依赖笔记本电脑,而编写程序需要的是对现实世界的事物进行抽象,抽象的结构就是有了抽象类和接口,然后我们根据系统设计的需要产生了抽象间的依赖,代替了人们传统思维中的事物间的依赖,“倒置”就是从这里产生的。
5 .合成/聚合复用原则(Composite/Aggregate ReusePrinciple ,CARP):要尽量使用对象组合,而不是继承关系达到软件复用的目的
定义:经常又叫做合成复用原则(Composite ReusePrinciple或CRP),尽量使用对象组合,而不是继承来达到复用的目的。
就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新对象通过向这些对象的委派达到复用已有功能的目的。简而言之,要尽量使用合成/聚合,尽量不要使用继承。
原则分析:
1)在面向对象设计中,可以通过两种基本方法在不同的环境中复用已有的设计和实现,即通过 组合 / 聚合关系 或通过 继承 。 继承复用 :实现简单,易于扩展。破坏系统的封装性;从基类继承而来的实现是静态的,不可能在运行时发生改变,没有足够的灵活性;只能在有限的环境中使用。( “白箱”复用) 组合/聚合复用 :耦合度相对较低,选择性地调用成员对象的操作;可以在运行时动态进行。( “黑箱”复用 ) 2) 组合 / 聚合可以 使系统更加灵活 ,类与类之间的 耦合度降低 ,一个类的变化对其他类造成的影响相对较少,因此一般 首选使用组合 / 聚合来实现复用 ;其次才考虑继承,在使用继承时,需要严格遵循里氏代换原则,有效使用继承会有助于对问题的理解,降低复杂度,而滥用继承反而会增加系统构建和维护的难度以及系统的复杂度,因此需要 慎重使用继承复用 。 3)此原则和里氏代换原则氏相辅相成的,两者都是具体实现”开-闭”原则的规范。违反这一原则,就无法实现”开-闭”原则,首先我们要明白合成和聚合的概念: 什么是合成 合成(组合):表示一个整体与部分的关系, 指一个依托整体而存在的关系( 整体与部分 不可以分开 ),例如:一个人对他的房子和家具,其中他的房子和家具是不能被共享的,因为那些东西都是他自己的。并且人没了,这个也关系就没了。这个例子就好像,乌鸡百凤丸这个产品,它是有乌鸡和上等药材合成而来的一样。 也比如 络游戏中的武器装备合成一样,多种东西合并为一种超强的东西一样。 虽然组合表示的是一个整体与部分的关系,但是组合关系中部分和整体具有统一的生存期。一旦整体对象不存在,部分对象也将不存在,部分对象与整体对象之间具有同生共死的关系。 在组合关系中,成员类是整体类的一部分,而且整体类可以控制成员类的生命周期,即成员类的存在依赖于整体类。 在UML中,组合关系用带实心菱形的直线表示。
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!