软件构造相关二
【ADT的基本概念】
抽象数据类型(Abstract Data Type,ADT)是是指一个数学模型以及定义在该模型上的一组操作;即包括数据数据元素,数据关系以及相关的操作。
ADT具有以下几个能表达抽象思想的词:
抽象化:用更简单、更高级的思想省略或隐藏低级细节。
模块化: 将系统划分为组件或模块,每个组件可以设计,实施,测试,推理和重用,与系统其余部分分开使用。
封装:围绕模块构建墙,以便模块负责自身的内部行为,并且系统其他部分的错误不会损坏其完整性。
信息隐藏: 从系统其余部分隐藏模块实现的细节,以便稍后可以更改这些细节,而无需更改系统的其他部分。
关注点分离: 一个功能只是单个模块的责任,而不跨越多个模块。
与传统类型定义的差别:
传统的类型定义:关注数据的具体表示。
抽象类型:强调“作用于数据上的操作”,程序员和client无需关心数据如何具体存储的,只需设计/使用操作即可。
ADT是由操作定义的,与其内部如何实现无关!
【ADT的四种类型】
前置定义:mutable and immutable types
可变类型的对象:提供了可改变其内部数据的值的操作。Date
不变数据类型: 其操作不改变内部值,而是构造新的对象。String
Creators(构造器):
创建某个类型的新对象,⼀个创建者可能会接受⼀个对象作为参数,但是这个对象的类型不能是它创建对象对应的类型。可能实现为构造函数或静态函数。(通常称为工厂方法)
t* -> T
栗子:Integer.valueOf( )
Producers(生产器):
通过接受同类型的对象创建新的对象。
T+ , t* -> T
栗子:String.concat( )
Observers(观察器):
获取抽象类型的对象然后返回一个不同类型的对象/值。
T+ , t* -> t
栗子:List.size( ) ;
Mutators(变值器):
改变对象属性的方法 ,
变值器通常返回void,若为void,则必然意味着它改变了对象的某些内部状态;当然,也可能返回非空类型
T+ , t* -> t || T || void
栗子:List.add( )
解释:T是ADT本身;t是其他类型;+ 表示这个类型可能出现一次或多次;* 表示可能出现0次或多次。
【设计一个好的ADT】
设计好的ADT,靠“经验法则”,提供一组操作,设计其行为规约 spec
原则 1:设计简洁、一致的操作。
最好有一些简单的操作,它们可以以强大的方式组合,而不是很多复杂的操作。
每个操作应该有明确的目的,并且应该有一致的行为而不是一连串的特殊情况。
原则 2:要足以支持用户对数据所做的所有操作需要,且用操作满足用户需要的难度要低。
提供get()操作以获得list内部数据
提供size()操作获取list的长度
原则 3:要么抽象、要么具体,不要混合 —— 要么针对抽象设计,要么针对具体应用的设计。
【测试ADT】
测试creators, producers, and mutators:调用observers来观察这些 operations的结果是否满足spec;
测试observers: 调用creators, producers, and mutators等方法产生或改变对象,来看结果是否正确。
OOP的基本概念
【对象】
对象是类的一个实例,有状态和行为。
例如,一条狗是一个对象,它的状态有:颜色、名字、品种;行为有:摇尾巴、叫、吃等。
概念:一个对象是一堆状态和行为的集合。
状态是包含在对象中的数据,在Java中,它们是对象的fields。
行为是对象支持的操作,在Java中,它们称为methods。
【类】
类是一个模板,它描述一类对象的行为和状态。
每个对象都有一个类
类定义了属性类型(type)和行为实现(implementation)
简单地说,类的方法是它的应用程序编程接口(API)。
类成员变量(class variable)又叫静态变量;类方法(class method)又叫静态方法:
实例变量(instance variable)和实例方法(instance method)是不用static形容的实例和方法;
二者有以下的区别:
类方法是属于整个类,而不属于某个对象。
类方法只能访问类成员变量(方法),不能访问实例变量(方法),而实例方法可以访问类成员变量(方法)和实例变量(方法)。
类方法的调用可以通过类名.类方法和对象.类方法,而实例方法只能通过对象.实例方法访问。
类方法不能被覆盖,实例方法可以被覆盖。
当类的字节码文件被加载到内存时,类的实例方法不会被分配入口地址 当该类创建对象后,类中的实例方法才分配入口地址, 从而实例方法可以被类创建的任何对象调用执行。
类方法在该类被加载到内存时,就分配了相应的入口地址。 从而类方法不仅可以被类创建的任何对象调用执行,也可以直接通过类名调用。 类方法的入口地址直到程序退出时才被取消。
注意:
当我们创建第一个对象时,类中的实例方法就分配了入口地址,当再创建对象时,不再分配入口地址。 也就是说,方法的入口地址被所有的对象共享,当所有的对象都不存在时,方法的入口地址才被取消。
总结:
类变量和类方法与类相关联,并且每个类都会出现一次。 使用它们不需要创建对象。
实例方法和变量会在每个类的实例中出现一次。
【接口】
概念:接口在JAVA编程语言中是一个抽象类型,用于设计和表达ADT的语言机制,其是抽象方法的集合,接口通常以interface来声明。
一个类通过继承接口的方式,从而来继承接口的抽象方法。
接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。
一个接口可以扩展其他接口,一个类可以实现多个接口;一个接口也可以有多重实现
除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。
接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。
总结:接口的好处
Safe from bugs
ADT是由其操作定义的,接口就是这样做的。
当客户端使用接口类型时,静态检查确保他们只使用由接口定义的方法。
如果实现类公开其他方法,或者更糟糕的是,具有可见的表示,客户端不会意外地看到或依赖它们。
当我们有一个数据类型的多个实现时,接口提供方法签名的静态检查。
Easy to understand
客户和维护人员确切知道在哪里查找ADT的规约。
由于接口不包含实例字段或实例方法的实现,因此更容易将实现的细节保留在规范之外。
Ready for change
通过添加实现接口的类,我们可以轻松地添加新类型的实现。
如果我们避免使用静态工厂方法的构造函数,客户端将只能看到该接口。
这意味着我们可以切换客户端正在使用的实现类,而无需更改其代码。
【抽象类】
抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。
由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。
父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法。
在Java中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。
如果一个类包含抽象方法,那么该类必须是抽象类。
任何子类必须重写父类的抽象方法,或者声明自身为抽象类。
构造方法,类方法(用static修饰的方法)不能声明为抽象方法。
OOP的不同特征
【封装】
封装(英语:Encapsulation)是指一种将抽象性函式接口的实现细节部份包装、隐藏起来的方法。
设计良好的代码隐藏了所有的实现细节
干净地将API与实施分开
模块只能通过API进行通信
对彼此的内在运作不了解
信息封装的好处
将构成系统的类分开,减少耦合
加快系统开发速度
减轻了维护的负担
启用有效的性能调整
增加软件复用
信息隐藏接口
使用接口类型声明变量
客户端仅使用接口中定义的方法
客户端代码无法直接访问属性
【继承与重写】
继承概念:继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
重写概念:重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
实际执行时调用那种方法,在运行时决定
重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。
子类只能添加新方法,无法重写超类中的方法。
当子类包含一个覆盖超类方法的方法时,它也可以使用关键字super调用超类方法。
上一篇: 面试开发人员的有效方法
下一篇: 软件构造学习笔记——重构