美文网首页
面向对象的设计思想

面向对象的设计思想

作者: Denley丶垒 | 来源:发表于2019-08-03 21:50 被阅读0次

    面向对象是一种设计的思想,与具体实现的语言工具无关。能让软件架构更符合人的思维模式,更为清晰明了,更易于理解与维护。这是大型软件必然的选择。

    面向对象的程序设计语言必须有描述对象及其相互之间关系的语言成分。这些程序设计语言可以归纳为以下几类:系统中一切事物皆为对象;对象是属性及其操作的封装体;对象可按其性质划分为类,对象成为类的实例;实例关系和继承关系是对象之间的静态关系;消息传递是对象之间动态联系的唯一形式,也是计算的唯一形式;方法是消息的序列。

    面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。

    面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。
    而面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。

    万物皆为对象,对象是对现实事物的一种抽象,通过程序来实现对事物的描述。面向对象编程的三大特征:封装、继承和多态。

    I thought of objects being like biological cells and/or individual computers on a network, only able to communicate with messages (so messaging came at the very beginning -- it took a while to see how to do messaging in a programming language efficiently enough to be useful).
    ...
    OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things. It can be done in Smalltalk and in LISP.

    简单解释一下上面的这几句话的大概意思:OOP应该体现一种网状结构,这个结构上的每个节点“Object”只能通过“消息”和其他节点通讯。每个节点会有内部隐藏的状态,状态不可以被直接修改,而应该通过消息传递的方式来间接的修改。

    我的评注: 在面向过程的方法中,我们头脑中首先出现的是类似流程图的的东西,而采用OOP我们头脑中首先出现的是类似对象关系图的东西。

    面向对象的本质和面向对象的程序语言:

    面向对象的本质是什么?答案是抽象。从面对的问题域抽象出解决问题所需的对象是面向对象方法的核心思想。能否恰当抽象出足够的对象类型,特别是抽象出潜在的对象是决定软件设计好坏的关键。如果从更宽泛的角度讲,对我们所面对的复杂问题进行抽象,抓住本质,得出高度精炼的逻辑模型,对问题的求解具有重要的意义。从这个角度来说,抽象并不仅仅局限于对象的抽象,也包括流程和更高层的系统结构。
    

    而封装、继承、多态是面向对象的特征,这是采用面向对象的方法所能够达到的效果。注意区分特点和本质。

    类的定义类具有属性和行为,它是将数据和与数据相关的操作封装在一起的集合体,类定义中的成员即成员变量和成员函数。

       面向对象是基于万物皆对象这个哲学观点. 把一个对象抽象成类,具体上就是把一个对象的静态特征和动态特征抽象成属性和方法,也就是把一类事物的算法和数据结构封装在一个类之中,程序就是多个对象和互相之间的通信组成的.
    
       面向对象具有封装性,继承性,多态性.封装隐蔽了对象内部不需要暴露的细节,使得内部细节的变动跟外界脱离,只依靠接口进行通信.封装性降低了编程的复杂性. 通过继承,使得新建一个类变得容易,一个类从派生类那里获得其非私有的方法和公用属性的繁琐工作交给了编译器. 而 继承和实现接口和运行时的类型绑定机制 所产生的多态,使得不同的类所产生的对象能够对相同的消息作出不同的反应,极大地提高了代码的通用性. 
    
       总之,面向对象的特性提高了大型程序的重用性和可维护性。
    

    更多思考

    分离变化

    分离变化是贯穿在整个面向对象设计中的核心思想,具有非常重要的指导意义。

    封装内部变化,扩展外部变化。封装内部变化自不必说,通过继承和多态扩展外部变化,这就是面向对象的三大特征啊。面向对象思想很大程度上就是面向变化的思想,把各种各样的变化封装为对象,合理的分配这些对象,当然这里的合理是有技巧的。

    软件设计应该基于不变的基石,分离变化的意义就在于此,帮助建立稳定模型。

    分析变化需要一定的经验,if-else语句暗示着变化,扩展的业务暗示着变化…

    转移变化需要一定的技巧,封装、依赖倒置、增加间接层…

    继承与组合

    面向对象系统功能复用的两大技术包括类继承和对象组合,二者选择有一句“格言”:优先使用对象组合,而不是类继承(Favor object composition over class inheritance)。这句话我很赞同,但我还想强调的是,二者的使用场景不同,并不能相互替换:类继承强调的是抽象复用(属于同一类)而对象组合强调的是实现复用(借用一下其他对象的行为实现)。而在实际项目中类继承常常被滥用于实现复用,我们应该消除此类过度继承,这才是我们强调组合优于继承的最主要原因。

    绝大部分设计模式都基于继承或者组合实现,甚至有的设计模式比如适配器模式使用继承和组合都是正确的,分别为类适配器和对象适配器模式。而有的设计模式比如桥梁模式则是典型的同时使用了继承和组合,分别负责抽象部分和实现部分的复用。

    抽象与扩展

    有抽象的地方就有扩展,需要扩展的地方就需要抽象。
    面向对象思想在这点上的支持非常好,假如现在有一个Object,抽象一下就变成了:

    // interface or abstract class
    public interface IAbstract {
    }
    public class ObjectA implements IAbstract {
    }
    public class ObjectB implements IAbstract {
    }
    

    这种结构我这边给取个名字”OU结构”,OU的意思是ObjectUnit,面向对象单元的意思,操作起来非常具体,后续文章中会大量用到这种方法。各个设计模式充斥着大量的这类代码,希望大家看到这样的一组结构,可以看成是“一个”类,一个包含着博大精深的面向对象思想的类,这会大大简化你对设计模式的理解。

    从具体的接口与实现角度分析,我们应该面向接口编程而不是面向实现编程。接口本身是抽象,实现就是遵循这种抽象规则的具体扩展。在面向对象中,inteface或者abstract class巧妙的把抽象和扩展连接起来了,这很关键。

    对象关系

    在java以及其他的面向对象设计模式中,类与类之间主要有6种关系,他们分别是:依赖、关联、聚合、组合、继承、实现。他们的耦合度依次增强。

    当我们在讨论耦合的时候,很多时候是在讨论对象的关系。对象关系主要包括以下五种:实现、继承、组合、聚合、关联、依赖。这些关系的耦合度依次递减(是不是体现了“组合优于继承”的思想)。
    应该准确的使用这些对象关系,而避免使用不合适的对象关系。

    其实很多书中,面向对象的关系不是六种,而是4种:依赖、关联、继承、实现。聚合和组合是比较特殊的关联关系。分为六种的其实就是把关联根据耦合度从小到大又分为了关联、聚合、组合。

    聚合:通过setter方法从传入其他类的参数进来 组合:在构造方法中实例化其他类。

    Public class People{
        Soul soul;
        Body body; 
        //组合关系中的成员变量一般会在构造方法实例化
         Public People(){ 
            soul = news Soul();
            body = new Body();
        }
    
    

    聚合是一种“整体-部分”的关系,组合也是一种“整体-部分”的关系,区别在于聚合中的整体和部分有着独立的生命周期,而组合中的整体和部分的是生命周期应该时一样的,java中实现貌似无法有体现这一点,而C++中能够体现。个人认为这些区别更多的体现在语义上,而非实现上。同时认为上面聚合例子不能体现整体与部分的关系。

    对象之间的关系:依赖(需要某种服务),关联(对象间有某种对应关系),聚合,组合,继承...

    依赖:对象之间最弱的一种关联方式,是临时性的关联。代码中一般指由局部变量、函数参数、返回值建立的对于其他对象的调用关系。 依赖一般情况下是以下几种情况之一:
    a、ClassA中某个方法的参数类型是ClassB; 这种情况成为耦合;
    b、ClassA中某个方法的参数类型是ClassB的一个属性; 这种情况成为紧耦合;
    c、ClassA中某个方法的实现实例化ClassB;
    d、ClassA中某个方法的返回值的类型是ClassB;如果出现了上述四种情况之一,两个类很有可能就是“依赖”关系。

    大多数情况下,依赖关系体现在某个类的方法使用另一个类的对象作为参数。

    依赖关系(Dependency):是类与类之间的连接,依赖总是单向的。依赖关系代表一个类依赖于另一个类的定义。下面的例子中class A 依赖与class B、C、D。

    public class A{ 
        public B getB(C c, D d){ 
        E e = new E();          
        B b = new B(c, d, e);
        }     
    } 
    

    关联:
    对象之间一种引用关系,比如客户类与订单类之间的关系。这种关系通常使用类的属性表达关联关系所涉及的两个类是处于同一层次上的,而在聚合关系中,两个类处在不平等的层次上的,一个代表整体,一个代表部分。(关联与聚合仅仅从语法上是区分不开的,需要察所涉及的类之间的逻辑关系。)关联是一种结构关系,说明一个事物的对象与另一个事物的对象相联系。给定一个连接两各类的关联,可以从一个类的对象导航到另一个类的对象。关联类通过一条虚线与关联连接关联可以有方向,即导航。一般不作说明的时候,导航是双向的,不需要在线上标出箭头。大部分情况下导航是单向的,可以加一个箭头表示。关联在代码中一般表示为属性(成员变量),例如下面例子中 class A与B关联public class A{ private B b; } 如果B也关联到A,那么它们就是双向的关联。public class B{ private A a; }

    关联和依赖的区别:
    从类之间关系的强弱程度来分,关联表示类之间的很强的关系;依赖表示类之间的较弱的关系;
    从类之间关系的时间角度来分,
    关联表示类之间的“持久”关系,这种关系一般表示一种重要的业务之间的关系,需要保存的,或者说需要“持久化”的,或者说需要保存到数据库中的。比如学生管理系统中的Student类和Class(班级)类,一个Student对象属于哪个Class是一个重要的业务关系,如果这种关系不保存,系统就无法管理。另外,依赖表示类之间的是一种“临时、短暂”关系,这种关系是不需要保存的,比如Student类和StuEditScreen(学生登录界面)类之间就是一种依赖关系,StuEditScreen类依赖Student类,依赖Student对象的信息来显示编辑学生信息。

    聚合(关联关系的一种):表示has-a的关系,是一种不稳定的包含关系。聚合类不必对被聚合类负责。使用集合属性表达聚合关系,当对象A被加入到对象B中,成为对象B的组成部分时,对象B和对象A之间为聚集关系。聚合是关联关系的一种,是较强的关联关系,强调的是整体与部分之间的关系与关联关系一样,聚合关系也是通过实例变量来实现这样关系的。关联关系和聚合关系来语法上是没办法区分的,从语义上才能更好的区分两者的区别。聚合关系(Aggregation):是关联关系的一种,是强的关联关系。聚合是整体与个体之间的关系。如汽车类与引挚类,轮胎类之间的关系就是整体与个体的关系。
    与关联关系一样,聚合关系也是通过实例变量来实现的。空心菱形关联和聚集的区别:
    (1)关联关系所涉及的两个对象是处在同一个层次上的。比如人和自行车就是一种关联关系,而不是聚合关系,因为人不是由自行车组成的。
    聚合关系涉及的两个对象处于不平等的层次上,一个代表整体,一个代表部分。比如电脑和它的显示器、键盘、主板以及内存就是聚集关系,因为主板是电脑的组成部分。
    (2)对于具有聚集关系(尤其是强聚集关系)的两个对象,整体对象会制约它的组成对象的生命周期。部分类的对象不能单独存在,它的生命周期依赖于整体类的对象的生命周期,当整体消失,部分也就随之消失。比如张三的电脑被偷了,那么电脑的所有组件也不存在了,除非张三事先把一些电脑的组件(比如硬盘和内存)拆了下来。

    四、聚合关系(Aggregation)

    聚合关系(Aggregation):表示的是整体和部分的关系,整体与部分 可以分开.

    • 聚合关系(Aggregation) 表示一个整体与部分的关系。通常在定义一个整体类后,再去分析这个整体类的组成结构,从而找出一些成员类,该整体类和成员类之间就形成了聚合 关系。
    • 在聚合关系中,成员类是整体类的一部分,即成员对象是整体对象的一部分,但是成员对象可以脱离整体对象独立存在。在UML中,聚合关系用带空心菱形的直线表示。

    如:电话机包括一个话筒 电脑包括键盘、显示器,一台电脑可以和多个键盘、多个显示器搭配,确定键盘和显示器是可以和主机分开的,主机可以选择其他的键盘、显示器组成电脑;

    五、组合关系(Composition)

    组合关系(Composition):也是整体与部分的关系,但是整体与部分不可以分开.

    • 组合关系(Composition)也表示类之间整体和部分的关系,但是组合关系中部分和整体具有统一的生存期。一旦整体对象不存在,部分对象也将不存在,部分对象与整体对象之 间具有同生共死的关系。
    • 在组合关系中,成员类是整体类的一部分,而且整体类可以控制成员类的生命周期,即成员类的存在依赖于整体类。在UML中,组合关系用带实心菱形的直线表示。

    如:公司和部门,部门是部分,公司是整体,公司A的财务部不可能和公司B的财务部对换,就是说,公司A不能和自己的财务部分开; 人与人的心脏.

    组合:表示contains-a的关系,是一种强烈的包含关系。组合类负责被组合类的生命周期。也使用集合属性表达聚合关系 ,是关联关系的一种,是比聚合关系强的关系。它要求普通的聚合关系中代表的对象负责代表部分的对象的生命周期,合成关系是不能共享的。
    代表整体的对象需要负责保持对象的存活,在一些情况下负责将代表部分的对象湮灭掉。代表整体的对象可以将代表部分的对象传递给另一个对象,由后者负责此对象的生命周期。换言之,代表部分的对象在每一个时刻只能与一个对象发生合成关系,由后者排它的负责其生命周期,(聚合和组合的明显的区别是:如果类B含有A类对象的指针,那算聚合(生存周期不一样),如果类B含有A类对象的变量为属性,那么就必为组合(生存周期必须相同)),实心菱形

    继承:表示is-a的关系,是对象之间耦合度最大的一种关系,子类继承父类的所有细节。直接使用语言中的继承表达。 类图中继承的表示方法是从子类拉出一条闭合的、单键头(或三角形)的实线指向基类
    从使用的频率来看,关联(包括聚合和组合)关系是使用最为广泛的,其次是依赖和继承
    设计类之间的关系是遵循的原则:
    首先判断类之间是否是一种“关联”关系,
    若不是再判断是否是“依赖关系”,
    一般情况下若不是关联,就是依赖关系

    聚合关系建议使用部门与员工来举例,组合关系建议使用公司与部门来举例。部门由员工组成,部门解散员工照样生活。公司由部门组成,公司破产倒闭,部门则不复存在。

    我觉得这个里面的聚合,是不是说错了?聚合难道不是部门和员工之间的关系么?应该强调的是同类群体和个体的关系,而不是汽车和汽车零件的关系吧,汽车和汽车零件应该是组合关系吧?两者不是一个类型。汽车离不开汽车零件,但是部门和员工之间的关系可不一样啊,部门去掉几个员工也没啥关系啊。
    比较同意你的看法。 聚合是has-a关系,集体和个体关系。组合是contain-a关系,整体与部分的关系,关联关系比聚合强。

    相关文章

      网友评论

          本文标题:面向对象的设计思想

          本文链接:https://www.haomeiwen.com/subject/wapsdctx.html