UML面向对象设计基础(异步图书)_note1_第二章面向对象发展简史之前的内容

Meliir Page-Jones. UML面向对象设计基础(异步图书). 人民邮电出版社. Kindle 版本.

序言:

  1. 需要在后面的阅读中关注以下这几点:

    1. 面向对象编程的新思想

    2. 对继承使用的剖析

    3. 如何针对对象类直接按疑难数据关系进行建模

    4. 注重细节以及基础的分析方法与编程作品

    5. 从长远利益角度建立合适的模型

    6. 学习一个好的面向对象的系统应该是怎么样的, 不应该是怎么样的

  2. 示例: 给一个牛挤奶的过程通过面向对象应该怎么实现:

    1. Cow对象一个消息令其产生Milk对象

    2. Milk对象一个消息, 令其脱离Cow

前言:

  1. 本书的写作目的:

    1. 鼓励编程人员在进行编程之前先进行良好的面向对象的设计

    2. 学习符号表示法, 面向对象的原则和术语

    3. 学习如何对一个设计好的系统进行评估和讨论

  2. 市面上有很多关于如何使用面向对象进行GUI应用开发的书籍,但是面向对象的开发逻辑并不限于GUI

  3. 开发方法学并不是本书的重点 (开发方法学接收有关需求分析, 库管理, 解释各种开发活动是如何有机结合在一起的. 可以作为未来学习的一个方向)

  4. 本书主要涉及的内容:

    1. 面向对象软件设计的基本概念, 符号表示, 术语, 准则, 原理

    2. UML标准以及符号

      1. UML对类的描述

      2. UML对关联, 聚合, 组合对象的描述

      3. UML对子类, 超类的层次描述

      4. 使用UML对于消息流动的描述

      5. 使用UML描述系统的状态

      6. 使用UML描述系统结构以及UI界面

    3. 面向对象系统中常见的结构

    4. 面向对象的设计原理:

      1. 共生性

      2. 2级封装

      3. 类所属的域

      4. 不同类的内聚程度

      5. 将状态空间以及行为的概念应用到类的层次结构上

    5. 对于同一需求的多种不同的设计, 进行相互的比较

    6. 给出一些如何组织给定类的方法, 达到高可用以及易维护的效果

    7. 关于如何设计抽象接口以及如何设计好的类

    8. 分析软件构件的特点以及优缺点, 在商业应用开发中回顾面向对象的知识点

  5. 面向对象软件是由对象以及其所属的类构成的软件, 一个对象是一个软件组件, 其操作与一系列变量有关.

  6. 类实现了一种类型, 它定义属于该类的一组对象

第一部分:

引言:

  1. 面向对象这一术语缺少其固有的含义, 因此造成了定义的随意性. 唯一被所有专家共识的面向对象的内涵是封装

第一章 面向对象的含义:

  1. 作者认为面向对象最重要的9个概念:

    1. 封装:

      1. 是将相关的概念组成一个单元, 其后可以使用一个名称对这个单元(概念集合)进行引用

      2. 广义封装的作用:

        1. 通过封装创建了子程序的概念

        2. 子程序通过程序的复用和跳转节省了计算机的内存资源

        3. 子程序形象的名称减少了开发人员的记忆负担

        4. 通过形成一个概念将具象的代码抽象为一个人脑可以感知的概念, 帮助进行更加复杂的建模

      3. 面向对象的封装和子程序的区别

        1. 面向对象的封装的结构更加的复杂

        2. 面向对象的封装是将表示状态的操作和属性包装成为了一个整体(对象类型), 使得对于状态的访问或者修改只能通过封装提供的接口进行

          1. 这里需要注意一下作者的描述, 表示状态的操作和属性, 也就是作者认为所有的对象的方法以及属性都是对于对象的状态的描述

        3. 面向对象的封装核心是对象, 一个对象由一系列操作和一系列属性组成.

          1. 每个操作是一个过程或者函数, 对于其他对象是可见的

          2. 属性是对象记忆的内容, 它只能通过对象的操作进行访问和修改

          3. 这里就涉及到了作者认为的面向对象编程的基本架构. 只有过程是可见的, 对象本身的状态是不可见的, 对象的状态必须通过过程进行获取

          4. 这些操作形成了对对象状态的一个保护层. 虽然一些编程语言支持将对象的属性设置为公开, 在本书中所有直接提到的属性和操作均为公有, 间接引用的属性则为私有.

          5. 由于本书主要讨论对象之间的相互关系, 所以没有对函数的实现进行讨论, 这里就引出了一个问题: 脱离函数的实现, 只在对象的粒度上设计系统是否会导致可拓展性受到影响? – 好像面向对象一个性质就是进行对象层面的代码重用, 所以如果某个函数需要被拓展但是对象其他部分需要被重用则通过重写进行处理

    2. 信息/实现隐藏

      1. 信息/实现隐藏根据信息和实现的不同, 分别对应了对象的属性和对象的函数(方法). 达成信息/实现隐藏其实是封装的结果

      2. 信息/实现隐藏的效果是某些信息或者实现方法被限制在封装结构的内部, 而限制外界的可见性

      3. 信息/实现隐藏也是封装方案好坏评价的一个维度, 一个好的封装应该能够做到对外界封闭实现的细节

      4. 这里也涉及到了一个重要的内容, 既然关键是进行隐藏, 如果减少向外输出的操作就可以减小接口对于内容的耦合. 也就更加容易对对象的接口进行抽象. 可以增加接口的适用范围 注意: 这个判断还没有进行验证 

      5. 进行信息/实现隐藏的价值:

        1. 降低软件复杂性, 使得软件设计决策局部化, 在对象内进行私有的设计决策, 对于整个软件整体几乎没有影响

        2. 限制了修改波及的影响, 为后续维护更改提供了支持

        3. 进行隐藏后的表现形式减弱了信息的内容, 对象外部的用户不会受到任何内部信息的干扰, 对象内部的信息也不会被外部的用户干涉 (例如被其他程序员引入不稳定的链接, 交叉运行软件库导致内容信息被异常修改)

    3. 状态保持

      1. 对象具有保持状态的能力

      2. 在面向过程编程的时候, 程序员创建的子函数是没有记忆的. 模块运行结束, 只是返回运行的结果

      3. 在面向对象编程的时候, 对象的函数则具有了一定的记忆能力, 可以更方便的跨越函数的调用进行状态的保存. 这在需要动态迭代的程序中非常重要. 如果使用传统的面向过程的编程方式, 所有的状态量都需要在main函数中暴露, 造成了结构的混乱以及对象间的相互影响

      以上的三个面向对象的属性: 封装, 信息/实现隐藏, 状态保持, 仍旧是处于 abstruct datatype (ADT)的范畴中. ADT的一个直接的实现就是C中的struct

      但是一下这六个性质才是面向对象超脱ADT的核心竞争力

    4. 对象标识

      1. 是指每个一对象(不考虑其所属类或当前状态)可以作为不同的软件实体被标识, 处理的特性. 通俗地说就是一个对象可以用唯一的信息和其他任何类别的任何对象进行区分 — 这个唯一的信息可以通过对象句柄机制提供

      2. 示例:var homl: Hominoid : = Hominoid.New;在进行Hominoid.New的创建过程的时候同时生成了一个全局唯一的句柄, 将这个句柄赋予了homl

      3. 句柄(实际上这个句柄被称作对象标识符 – object identifier -- OID)的两个基本规则

        1. 在任何状态下, 对象在整个生命周期中保持使用同一个句柄

        2. 任意两个对象不可能具有相同的句柄. 只要是在同一个程序中, 不论是过去, 现在和未来, 程序中出现的任何一个对象都有唯一的标识符

      4. 对象标识符的其他属性:

        1. 对象标识符一般通过面向对象的编程环境自动的创建

        2. var homl: Hominoid : = Hominoid.New;: =更形象的描述是现在指向或者现在引用, 可以理解为homl变量保存了创建的对象的句柄

        3. 没有人可以在真正运行时看到新对象的句柄, 除非使用调试程序进行内存调试

        4. 理想的句柄是随机且唯一的. 但是一些面向对象环境中一般使用对象的物理内存地址作为句柄. 当对象在内存中移动的时候以及将对象交换到硬盘中的时候, 这样的句柄策略可能会出现问题

        5. 句柄也是对于对象是否被引用的关键判断依据, 一般用于配合垃圾回收

      5. 现在看来, 每个对象拥有其唯一的句柄是天经地义的, 但是这个设计对面向对象的软件开发造成了深刻的影响

    5. 消息

      1. 对象通过消息通知另一个对象进行某种活动. 消息还有将信息从一个对象传递到另一个对象的作用. 许多老前辈都将消息作为面向对象的重要特征

      2. 消息是发送对象接收对象发送请求的载体, 是申请接收对象的一个方法 – 换言之, 消息是发送对象的一个方法, 用于申请接收对象

      3. 消息的组成:

        1. 消息接收对象的句柄

        2. 消息发送对象希望消息接收对象进行的操作的名称

        3. 消息接收对象执行该操作所需要的附加信息

      4. 消息的实现:

         homl.turnRight; // 是一个消息, 
         // homl是消息接收对象的句柄, turnRight则是消息接收对象需要进行的操作
         // 但是注意到这个消息没有明确的发送者, 可以知道消息的发送者是程序开发人员
         消息的实现是通过在消息接收对象中创建对应的函数进行的.
         homl.advance(int oneSquare, out advanceOk);
         // homl 是目标对象
         // advance 是操作名称
         // oneSquare 是输入参数
         // advanceOk 是输出参数
         // 这里的操作名称, 输入参数, 输出参数 共同组成了消息的内容, 也称为 几乎目标操作的原型(signature)
      5. 面向对象中的消息和面向过程中的消息的区别:

        1. 面向过程的消息中, 我们先申请一个过程单元, 然后向其提供需要被操作的对象

        2. 面向对象的消息中, 我们先申请一个对象, 然后令该对象执行某个过程

        3. 以上的区别主要还是观念上的. 将一个对象被动接受过程更改为对象主动执行过程.

      6. 消息的参数:

        1. 消息的参数反映了面向对象软件和面向过程软件的一个重要区别, 在传递较为复杂的消息的时候, 消息的参数不再是数据而是对象的句柄. 所以消息的参数可以是句柄

      7. 消息的表示:

        1. 在UML中以以下的方式进行消息的表述

      8. 消息传递中的角色:

        1. 消息的发送者

        2. 消息的接收者

        3. 消息接收者内部的属性

        4. 消息传入和传出的参数

        5. 同一个对象可以既是消息的发送者也是消息的接收者

      9. 消息的类型:

        1. 报告消息:

          1. 向消息接收者传递某个消息, 要求对象进行自我更新

          2. 是一种面向过去的消息, 通知对象已经发生了什么

          3. 最典型的实践是Set(...)函数.  注意, 作者认为employee.got(MarriageDate: Date)是一个报告消息. 这里的重点是以对象为中心的函数命名方式. employee获取到了其结婚的日期, 于是更新了其内部的记忆, 我们平时进行函数命名的时候是以外界调用者为核心的. Set 是将employee中关于结婚日期的记忆设置为输入的日期. 所以UML实际上是以对象为中心的,不是以调用者为中心的. 这一点要特别注意

        2. 询问消息:

          1. 要求消息接收者返回某个消息,

          2. 最典型的实践是Get(...)函数.

          3. 也称读消息, 向后, 回拉消息.

          4. 其是一种面向现在的事情. e.g.homl.location()

        3. 祈使消息:

          1. 要求消息接收者开始执行某个操作. 请求对象对自身, 另一个对象, 系统环境, 进行一定的操作,

          2. 是一种面向未来的消息. 请求对象将来完成某些操作.

          3. e.g.homl.goToLocation(square: Square, out feasible: Boolean)

          4. 在一些实时的面向对象的系统中, 如果需要对象控制一些硬件则需要使用祈使消息

      10. 这个消息体现了作者一个很重要的逻辑思路:

        1. 面向对象的核心是封装. 我们并不关心对象内部是如何进行实现的. 我们只是通过函数对对象输入命令. 令对象按照命令进行执行

        2. 为什么不使用有过程意义的函数对对象的接口进行命名, 而通过信息对对象的接口进行命名. 因为作者要告诉我们, 接口的调用只是为对象提供了命令的信息, 不能保证对象执行的过程

      1. 对类进行实例化创建对象实例. 通过相同类创建的实例是结构相同的, 也就是拥有相同的操作和变量

      2. 类是创建对象的模板. 从同一个类实例化出的每个对象具有相同的结构和行为

      3. 区分类和对象:

        1. 每一个对象拥有不同的句柄

        2. 在不同的时刻, 同一个对象可能有不同的状态

        3. 对象是在运行时通过类进行创建的

        4. 类的核心意义是模板, 是简化对象创建的方式. 对象的核心意义是组件, 是参与实际运算的一个结构和行为的集合

      4. 类的每个对象(实例)都具有自身实现操作的一组方法和实现属性的一组变量的拷贝. 在给定时间内, 原则上来说, 有多少对象被实例化, 就有多少方法和变量被拷贝. 但是实际计算机中为了节省内存空间, 相同类实例化出的所有对象都拥有相同的方法的物理拷贝

      5. 概念辨析:

        1. 方法(函数, 过程) 是 操作 的实现

        2. 变量(字段) 是 属性 的实现

        3. 句柄 是 对象标识符 的实现

      6. 实例函数, 实例变量, 类函数, 类变量

        1. 实例函数和实例变量是和实例进行绑定的. 只有在指明使用什么实例之后才能使用它们

        2. 类函数, 类变量的作用对象是类, 不能和任何的对象进行绑定. e.g. New()函数就是类函数, 其用于进行从模板到实例的构建过程, 因此不能与实例绑定. 这里就可以得到一个很有意思的推论: 类的核心作用是通过对对象建立模板而简化对象创建的工作. 那么除了new以外的其他类函数和类变量的意义是什么呢? 那就是通过修改模板的参数, 或者提供独特的创建方式, 使得创建对象的过程更加的灵活与高效

      7. 类和抽象数据类型之间的区别:

        1. 抽象数据类型描述的是接口, 描述的是类可以向用户提供的功能, 但是不负责进行实现

        2. 类是实现抽象数据类型的具体内容

        3. 对于同一个抽象数据类型可以创建许多不同的类

    6. 继承

      1. 继承这一技术开发的环境: 由于代码中会出现相似的类, 或者对已有的类进行一些修改或者拓展. 则导致需要进行对许多类的实现的手动复制. 通过创建继承这一技术进行自动的复制. 方便进行维护以及批量创建拥有相似功能的类

      2. 继承的一种形象化的描述方式是请求使用其操作

      3. 继承是隐式地在子类中定义父类的属性和操作, 就像它们是被定义在子类中一样

      4. 类的意义是方便对于实体进行创建, 继承的意义是通过在不同的类之间进行拷贝以方便类的创建

      5. 继承的效果是可以帮助用户更高效的构造软件 — 继承对于高效构建的实现方式是这样的: 首先构造类对一般情况进行处理, 然后对于特殊情况通过继承进行类的创建

      6. 继承在类之间从一般到特殊的方式进行的类的创建. 这也就意味着要想设计出一个通过继承实现拓展的系统, 就需要对于软件的需求以及各个组件有非常充分的认识, 以至于可以从所有不同的建模方式中选取出最好的建模方式, 并抽象出最上层的对象. 但是实际中, 由于需求的不断变化以及产品定位的不断变化. 一个人是很难对项目进行高屋建瓴的抽象的. 这就体现了软件开发特别是软件设计中经验的重要性. 以及许多敏捷开发框架的主要设计需求 – 从不完整的建模走向完整的建模

      7. 由继承引出的对象实例的辨析

        1. 一个代码组件, 如果其是一个对象, 那其必定是一个实例. 如果是一个实例则也就是一个对象

        2. 但是实例是和类绑定的. 一个实例可以是多个类的实例, 所以一个对象也可以是多个类的实例

      8. 单继承和多继承:

        1. 单继承: 限制每个类最多有一个直接超类 – 形成的继承网络是一棵树

        2. 多继承: 每个类可以有任意多个直接超类 – 形成的继承网络是一个图 – 因为多继承会造成一些设计上的问题, 所以有些语言中不支持

          1. 如果子类从多个父类继承, 如果继承的操作或者属性中有相同的名字, 如何确定继承的是哪个操作

          2. 但是多继承在实际开发中确实是有其作用的. 但是由于会造成理解以及实现的问题, 所以Java中没有实现

    7. 多态性

      1. 多态性的两个定义:

        1. 多态性是一种方法, 这种方法可以在多个类中定义同一个操作或者属性名, 并在每个类中有不同的实现

        2. 多态性是一个特性, 这种特性使得一个属性或者变量在不同的时期可以表示不同的对象.

      2. 多态性的一个例子:

        1. 定义多边形类 Polygon, 其有子类Triangle, Rectangle, Hexagon. 不同的子类对于getArea方法有不同的实现. 现在有一个twoDShape对象. 已知其是Polygon的实例. 其getArea方法具体是哪个类定义的? – 因为一个对象可以是多类的实例, 所以需要设计机制判定这里调用的getArea方法是哪种实现 – 这就是多态

      3. 多态性的实现方式: 动态绑定

        1. 动态绑定是一种在运行时而不是编译时确定被执行代码的技术

        2. 动态绑定的效果是产生操作的覆盖(overriding)或者操作的重载(overloading).

          1. 覆盖是指父类定义的方法在子类中被重新定义 – 同一操作名称在纵向的类关系中重复定义 – 通过消息发送的目标对象的类进行纵向的动态绑定

          2. 重载是指在同一个类中定义多个拥有相同标识符的操作 – 同一操作名称在横向的单一类中进行重复定义 – 通过消息发送的消息参数的类或数目进行横向的动态绑定

      4. 覆盖机制对于面向对象的影响:

        1. 很多编程语言中除了可以进行修改性的覆盖以外还可以进行禁用性的覆盖: 例如在调用覆盖函数的时候返回错误以禁用父类的接口

        2. 这种进行进行禁用的覆盖机制如果只在小范围内使用对于面向对象的结构没有太大负面影响,但是如果进行大量使用会导致超类和子类之间的继承关系发生变质, 减少代码的继承拓展性

    8. 一般性

      1. 一般性指的是一个或多个类内部使用的类(EgClass– 这个名称的意思是有一个类叫做EgClass)的结构, 仅在运行时(即是实例化EgClass对象时)才提供

      2. 一般性的实际意义:

        1. 在进行代码开发的时候其实很多时候我们需要对于一种数据结构在不同的内部参数的情况下多次复制(e.g. vector<char>, vector<int> 如果没有Vector这一模板, 我们需要将Vector的所有数据结构全部复制一份然后更改其存储类型)

        2. 进行相同代码的复制意味着将单一的Bug进行复制, 获得多个同类Bug. 如果需要进行修改或者维护会导致很高昂的成本

      3. 一般性的一个典型实现: C++的模板类(又称为 参数化类)

        1. 如果不创建模板类技术也可以通过继承的方式, 创建内容类型为总父类的容器类. 但是由于总父类需要对所有的子类进行抽象, 而子类的范围无法在事先进行有效的界定. 所以会导致总父类定义的困难

        2. 模板类这一技术主要应用于容器类这一特殊的群体上. 因为容器类具有算法和内部对象类型分离的特点. 如果一个类的算法和其内部的对象类型紧密相关, 则一般很难抽象成为模板类

  2. 作者自定义的伪代码范例

     需求: Hominoid只能走过空白的位置,不能穿过墙壁也不能在墙壁内移动. 要求Hominoid可以从S走到F
     Class External-Interface Specifications(of the classes in the library) Hominoid
      New: Hominoid // 创建新示例
     turnLeft // 左转90度
     turnRight // 右转90度
     advance (noOfSquares: Integer, out advanceOK: Boolean )
     // 朝着面对的方向试图前进一定数量, 饭后是否前进成功
     location: Square // 获取当前所在的方块的对象
     facingWall: Boolean // 返回机器人是否正对着有墙的方块
     display //在 屏幕 上 显示 机器人 图标
     
     Grid
      New: Grid // 创建新的Grid实例
     start: Square // 返回起始点方块对象
     finish: Square // 返回结束点方块对象
     insertHominoid (hom: Hominoid, location: Square, out insertOK: Boolean)
     // 将某个Hominoid对象放在一个location上, 并返回是否放置成功
     display // 显示作为屏幕图案的方格
     

     

     

  3. 作者自定义的编程语言的语法

     以小写开头的单词表示对象, 实例操作, 实例属性
     以大写字母开头的单词表示类名, 类操作, 以及类属性
     insertHominoid (hom: Hominoid, location: Square, out insertOK: Boolean)
     表示这是一个Hominoid和Square类对象的操作, 返回值为一个Boolean对象. 使用out关键字进行输入和输出的分割
     : = 赋值操作符
     var insertOk 表示创建一个名称为insertOk的变量
  4. 对于上面的示例构建的代码:

     var grid: Grid: = Grid.New; // 创建一个名称为grid的Grid类的实例变量
     var homl: Hominoid: = Hominoid.New; // 创建一个名称为homl的Hominoid的实例变量
     var insertOk: Boolean;
     var advanceOk: Boolean;
     var startSquare: Square;
     const oneSquare = 1;
     startSquare: = grid.start;
     grid.insertHominoid(homl, startSquare, out insertOk); // 注意这个代码的意义是这个函数通过传入homl和startSquare然后将结果输出到insertOk变量中
     // 问题: 既然startSquare是grid实例对象中的一个参数, 为什么在进行grid.insertHominoid的时候还需要进行这个参数的输入?
     // 个人思考: 这里的意义可能是操作者试图将homl对象放置到startSquare对应的方格中. 如果方格不是初始方格则放置失败, 返回错误
     
     if not insertOk
     then abort everything!; // 这里abort everything相当于exit(1)
     endif;
     
     repeat 4 times max or until not homl.facingWall // 这里是一个循环, 循环跳出的循环有两个: 循环次数大于等于4次 或者 找到了一个方向使得机器人没有面对墙壁
      homl.turnLeft;
     endrepeat; // 这里实际上是一个 repeat ... until 语句, 这里列出的循环条件不是进入循环的条件而是跳出循环的条件
     
     grid.display;
     homl.display;
     
     // 好像作者试图使用这个循环进行深度遍历,但是这个算法本身是有问题的.
     // 如果存在一个岔路导致机器人的正面,左侧,右侧都是墙壁这由于作者没有设计掉头的功能, 会导致程序陷入死循环,无法结束
     repeat until homl.location = grid.finish
      if homl.faceingWall
      then homl.turnLeft
      if homl.facingWall
      then homl.turnRight; homl.turnRight;
      endif;
      endif;
      homl.advance(oneSquare, out advanceOk);
      homl.display;
     endrepeat
     
     
  5. 本章小结

    1. 对象是将表示对象状态的属性变量描述对象行为的函数操作耦合起来形成的编程实体

    2. 对象的函数操作负责处理表示对象状态的属性变量

    3. 对象的函数操作属性变量掩盖与中介效果被称为封装

    4. 好的封装能够使得信息具有局部性, 使内部实现免受外部修改的干扰, 也避免内部修改对于外部结构造成干扰. (内防扩散, 外防输入)

    5. 对象的属性变量使得对象具有了状态保持的能力

    6. 对象标识是每个对象全局唯一且永久的标识.

    7. 对象标识对象状态无关.

    8. 对象标识表现形式一般为对象句柄

    9. 消息是一个对象对另一个对象的命令信息.

    10. 消息表现形式一般为消息目标对象的函数操作的调用.

    11. 消息目标对象句柄,目标对象操作名,操作的输入参数,操作的输出参数构成. 在纯面向对象环境中参数种类只能是对象(不能是指针或者数据结构)

    12. 对象生成的模板.

    13. 从同一个衍生出的对象具有相同的结构和行为.

    14. 对象衍生过程被称作实例化. 也因此对象也被称为某个类的实例

    15. 通过定义一组类操作和一组类属性,令其所有实例都有相同的结构和行为.

    16. 之间可以形成超类子类层次结构这种层次结构被称作继承.

    17. 继承允许子类的实例使用超类定义的操作和属性,并支持在子类中对超类中已定义的操作或属性进行重定义. 这种重定义被称作覆盖 -- overriding

    18. 由于覆盖导致一个子类实例的某个操作有多层定义. 为了灵活地选择该操作的不同定义, 面向对象的设计者创造了对象系统的一个特殊功能. 这种功能叫做动态绑定, 由这个功能造成的结果叫做对象的多态性

    19. 动态绑定功能分为两种不同的子功能.

    20. 基于消息目标对象类型进行动态绑定的功能被叫做对覆盖的动态绑定

    21. 基于消息输入参数的类型与数量进行动态绑定的功能被叫做对重载的动态绑定

    22. 由于有了对重载的动态绑定, 在一个类中可以定义多个拥有相同名称但是输入参数不同的函数操作. 这种定义相同名称但是输入参数不同的函数操作的行为, 被称作函数的重载

    23. 针对一种特殊的具有内部数据类型和核心算法相互独立属性的类, 为了拓展其代码重用的范围, 加强类的一般性, 面向对象系统提供了参数化类(又称模板类)的特殊功能.

    24. 因为一些参数化类中将内部数据类型作为需要设置的参数, 并且具有只有输入了需要的参数才是完整的类的属性. 所以开发人员们形象地称呼这种类为容器类

  6. 章节习题;

    1. a: 对于代码的健壮性的衡量方法不是非常了解不知道如何对其进行修改 这里再参考答案中是要求进行异常情况的判断, 对于数据以及结构进行安全检查

      b: 一个函数的操作的相关数据分为输入参数, 对象属性, 输出参数. 由于这里作为输入的location参数在对象属性中可以获取, 所以没有必要进行参数的输入 这里的参考答案写的非常好: 对于grid对象没有必要知道homl对象的初始位置, 因为其内部已经保存了初始位置的信息, 相反根据homl数据的接口进行反推, homl 中需要保存所处的位置信息. 但是homl没有设计setLocation操作. 所以应该删除grid.insertHominoid操作, 而创建homl.insertIntoGrid操作和grid.isAWallLocation操作. 一边进行是否插入不可用位置的判断, 一边在homl对象中更新其位置信息. — 所以引入的时候关于是奶脱离牛还是牛产生奶的问题有了一个思路: 需要按照每个类的具体数据属性进行实现. 使用尽可能少的操作进行属性的更新

    2. 对象可以知道自己的句柄, 在C++中通过对象的this指针即可获得对象自身

    3. 我觉得这个问题本身描述的内容就并不正确. 例如一个对象的操作的效果是对输入的一个对象进行一定的修改在返回修改后的对象.(例如在C++中对于ostream <<操作符的重载) 所以使用相同对象同时作为输入和输出参数是可以理解的. 这里说不使用相同的参数名. 注意到不同的参数名可以绑定同一个对象句柄. 所以我觉得一般不使用相同的参数名的主要原因是为了方便编程者觉得对象经过了修改. 对于面向对象而言没有什么实际意义

       参考答案:
       因为这样做会使消息的目标改变了其中一个参数的句柄,这是一种拙劣的设计方法。消息的 发送者有权保证其变量的句柄在发送消息的前后保持一致。少数语言禁止这种方法;在其手册 中有这样的说明:“参数通过传送值进行对象访问,且不能被目标操作代码改变。”

      我觉得这里的意思是在作者认为的面向对象系统中函数操作的作用是修改自身的属性变量, 而不是修改其他对象的属性变量. 所以要求所有的输入参数所指向的对象不能被更改, 以减少对象内外的互相干扰. 可能是这种建模思路方便系统的建立. 但是在目前的一些编程语言中,其实可以不对输入参数保持只读. 这里我和作者思路的矛盾可能就是为什么我在建立面向对象模型的时候经常出现问题. 因为人为的加入了耦合元素导致建模混乱!!

    4. 对象的定义是属性变量和函数操作的集合体. 所以说整形和实数型实例也是对象这一说法是正确的. 因为整形,实型的对象是与其相关的操作进行了绑定的. 但是形式上, 这种绑定和判断是隐式的. 例如int(1)+float(2.2)这里的+操作就是和int进行了绑定的, 但是无法从编程语法上明显的看到.

      对于是否所有的编程实体都是对象这个问题, 我觉得可以这么认为. 例如原始数据类型是函数操作被隐式绑定的对象, 函数类型是没有对象属性只拥有对象函数操作的对象. 但是这样的认识对于编程建模而言没有太多意义, 这主要的意义其实是编程语言的设计

      如何创建这些非显式的对象: 直接通过复制操作或者是显式的转换创建新的对象. 对于函数而言, 通过函数的定义隐式的创建函数对象

      在一些严格的面向对象的编程语言如Smalltalk中有较为严谨的对象定义. 例如Integer或者Date这一类不需要通过手动调用new函数的类被称作文字类 -- literal classes. 这些类的实例中的属性就是其表示的值. 由于一些文字类型的对象是不变的, 所以有的编程语言通过预先定义的方式进行处理, 所以不支持用户手动的实例化操作.

    5. 这里涉及到类操作的设计目的以及其作用. 类操作不是和对象进行绑定的, 肯定是无法访问只有实例化之后才被构造的对象属性. 而且类操作是直接与类进行绑定的, 其意义是辅助类型(这一创建对象的模板)进行更多样的对象创建, 以及定制化的对象初始内容. 所以类操作其实和实例化后的对象没有任何的关系, 只有处于实例化过程中以及进行实例化前的对象才和类操作相关. 所以类操作无法访问被实例化后生成的对象属性是非常正常的

      作者认为关键问题是类操作需要知道要获取的是哪一个对象的属性.

    6. 没有细数…也不想数 作者在这里是进行New这一方法的被调用次数进行统计

    7. 首先所有的面向对象都有个程序的入口, 基于一切都是对象的思路. 程序员设计的主处理流程也是一个对象. 所以面向对象的程序的初始化方式是通过面向对象系统初始化进入程序的入口对象并让该入口对象创建与使用各种不同的对象

      作者的视角比我的更广, 作者认为有三种方式:

      1. 通过主函数开始进入程序, 通过主函数创建与控制对象

      2. 通过系统自动实例化一个初始对象, 通过这个初始对象创建与控制其他对象

      3. 通过创建一个面向对象的监听环境, 例如GUI对不同UI控件的监听, 通过用户的交互向适当的类进行消息的发送

      在这个问题的参考答案中我终于明白了一件事. 面向对象很可能最初是从UI控件的面向对象开始开发的, 而作者为什么使用例如消息这种在我看来模糊不清的概念,也是因为其出发点为UI控件. 对于UI控件的控制, 以消息作为控制方式是很合理的

    8. 这个问题就很奇怪. 面向对象其实是建模方式, 严格上来说面向对象并不限制如何将对象存放入内存或者硬盘中. 即是我将所有变量都处理为全局变量, 将所有可以展开的函数调用全部展开, 只要在面向程序员的时候, 程序可以被展示为面向对象的形式, 那么这个程序就是面向对象的. 所以对于计算机, 无所谓对象, 更无所谓对对象的影响.

      但是如果这里的对象专指对象拥有的数据或者函数操作. 那么根据该对象是否被持久化, 可以分为对象消失或者对象被存入硬盘

    9. 类是创建对象的模板, 就如我上面所说, 对于计算机而言没有类的概念. 但是如果就组成抽象的类的那些信息而言, 由于其是软件静态数据的一部分, 如果该软件不是允许完就立即删除, 则所有的类应该是被存放入硬盘中.

      作者参考答案中的描述: 类是永久的不变的单元, 但是对象是在运行时生成的易变的运行模块

    10. 没看懂这题目在问啥……这翻译该打

       参考答案: 在一些语言中,不守规矩的设计者可以设计一些方法,使外界可以“跳过对象的 围墙”并直接处理变量。一种方法是通过C++的“友元”方法。与多数其他设计过失一样,这种方法通常具有影响大的罪名。

      我真的吐了, 这翻译要么是用的机器翻译要么是没学过计算机. 但是我觉得这本书的内容还是很不错的. 这个参考答案的大概意思我还是明白的, 友元等跳过面向对象的封装的策略对于设计有很大的负面影响

    11. 这个问题中提供的信息非常有意思. 编程的环境被封为以下四个范围

      1. 对象结构(object-structured) 封装和状态保持性

      2. 基于对象(object-based) 对象标识性

      3. 基于类(object-class) 类的概念

      4. 面向对象(object-oriented) 继承和其他特性

      这里展现的其实是面向对象概念的发展历程. 现在工程领域的编程语言除了C语言以外基本都处于面向对象的层次. C语言处于第一层或者第二层之间. 因为其有struct这一封装方式, 但是对于对象的标识还是使用的内存空间地址, 而内存地址还是可能发生重复的, 无法满足全局唯一性

    12. Java中的extend是对于父类进行继承, 而implement是对接口进行实现. 一个类实现多个接口是许多编程设计中的需要. 但是对于多继承由于涉及到父类内部的属性, 函数方法, 容易导致继承错误. 所以一般不支持多继承

      作者的参考答案: extend在继承了接口的同时还继承了能力, 但是implement只继承了接口和需要实现这些接口的责任, 但是没有继承这些接口的能力

    13. 这么无聊的事情我才不想去做

    14. 这个我还真不清楚该如何分析…… 如何从软件的功能与特性上判断一个软件是否是通过面向对象实现的……但是我觉得现实中其实很多软件是同时具有面向对象与面向过程属性的. 所以我觉得对于一个软件界定其是否为面向对象实现有点困难.

      真的太草了, 这个这么重要的问题居然没有参考答案!

posted @ 2021-11-10 12:31  NoobSir  阅读(140)  评论(0编辑  收藏  举报