软件开发就是将抽象思维转化成底层思维
底层思维:向下,把握机器底层,从微观理解对象构造
语言构造
编译转换(编译过程做的事情)
内存模型(运行到内存中)
运行时机制(异常机制,java的GC)
抽象思维:向上,将我们的世界抽象为程序代码
面向对象
组件封装
设计模式
架构模式
向下需要理解面向对象的三大原则
- 封装
- 继承
- 多态
向上要深刻把握面向对象机制所带来的抽象意义,理解如何使用机制来表达现实世界
如何解决需求变化带来的复杂性
- 分解:即分而治之,将大问题分解为多个小问题,将复杂问题分解为多个简单问题
- 抽象:用一个通用的技术,即抽象,由于不能掌握全部的复杂对象,我们选择忽视它的非本质细节
,而去处理泛化和理想化了的抽象模型。
1.策略模式
设计鸭子类
设计原则:
- 找出应用中可能需要变化之处,把它们独立起来,不要和那些不需要变化的代码混在一起
(即将可变之处抽象出来进行封装,好让其他部分不会受到影响)
(把会变化的部分取出来并封装起来,以便以后可以轻易改动或扩充此部分,而不影响变化的其他部分) - 针对接口编程,而不是针对实现编程
(对象的行为将被放在分开的类中,此类专门用来提供某行为接口的实现,这样鸭子类就不再需要知道行为的具体细节)
”针对接口编程“的真正意思是“针对超类型编程”
接口在这里有多个含义:
接口是一个“概念”,也是Java的interface构造。可以在不涉及Java的interface的情况下, “针对接口编程”,就用多态。
利用多态,程序可以针对超类型编程,执行时会根据实际状况执行到真正的行为,而不是被绑死在超类型的行为上。
“针对超类型编程”这句话,可以更明确地说成“变量”的声明类型应该是超类型,通常是一个抽象类或者是一个接口,
如此,只要是具体实现此超类型的类所产生的对象,都可以指定给这个“变量”,
意味着,声明类时不用理会以后执行时的真正对象类型。
例如:动物中狗的狗叫行为
针对实现编程的写法:Dog dog = new Dog();//Dog是Animal的具体实现,会让我们必须针对实现进行编程
dog.bark();//bark是狗叫行为
针对接口编程的写法:Animal animal = new Dog();
animal.makeSound();//狗类实现动物类接口后,重写makeSound方法,在里面具体实现狗叫行为
做法的对比
旧:行为来自鸭子的具体实现,或是继承某个接口并由子类自行实现得来。这两种都是依赖于实现,如果要变更行为,就需要写很多的代码
新:鸭子的子类将使用接口与所表示的行为,所以实际的“实现”不会被绑死在鸭子的子类中。特定的具体行为在实现了接口与所表示的行为中。
实现鸭子的行为
创建飞行行为的接口:
给不能飞和能飞的接口提供实现
呱呱叫的行为接口
给各种鸭子类的行为类型提供实现
这样子设计,可以让飞行和呱呱叫的动作被其他的对象复用,因为这些具体的行为已经于鸭子类无关了
而新增的行为也不会影响到现有的行为,也不会影响到“使用”飞行行为的鸭子类。
这样一来,有了继承的“复用”,却没有继承所带来的包袱。
问题:
在做系统时,需要先看看哪些地方需要变化去把这些地方分离&&封装,还是做完才去分离&&封装?
- 在设计系统时,就需要考虑到哪些地方在未来会发生变化,就提前在那些代码加入弹性;原则与模式可以应用在软件开发生命周期的任何阶段。
- Duck是否也该设计为一个接口?
不合适,因为一切都已经设计妥当,而且让Duck成为一个具体类,这样可以让衍生的特定类(如绿头鸭)具有共同的属性和方法。这样做是把Duck的继承结构中删除了变化的部分,所以不需要再将Duck设计为接口。 - 用一个类代表一个行为,但类不应该是代表某种“东西”吗?类不是应该同时具备状态“与”行为吗?
在OO系统是这样的,类代表的东西一般都是既有状态(实例变量)又有方法。只是在鸭子例子中,碰巧“东西”是个行为。但即使是行为,也可以有状态和方法。比如飞行的行为具有实例变量,记录飞行行为的属性(飞行高度,速度)
整合鸭子的行为
现在鸭子会将飞行和呱呱叫的动作“委托”给别人处理,而不是将呱呱叫和飞行的方法定义在Duck类或其子类。
大致的做法如下:
首先,在Duck类中“加入两个实例变量”,分别为“flyBehavior”和“quackBehavior”,声明为接口类型,而不是具体类实现类型,每个鸭子对象都会动态地设置这些变量在运行时引用正确的行为类型(例如:FlyWithWings、Squeak)
然后必须将Duck类与其所有子类中的fly()与quack()删除,因为他们已经在FlyBehavior和QuackBehavior中存在了。
用两个相似的方法performFly()和performQuack()取代Duck类中的fly()与quack()。
在Duck 中做的方法都是从接口中调用的,Duck的代码如下:
public class Duck{
QuackBehavior quackBehavior;//每只鸭子都会引用实现QuackBehavior接口的对象
public void performQuack(){
quackBehavior.quack();//鸭子对象不会亲自处理呱呱叫的行为,而是委托给quackBehavior引用的对象
}
}
更多的整合
如何设定flyBehavior与quackBehavior的实例变量?
看看MallardDuck类
public class MallardDuck extends Duck {
public MallardDuck() {
quackBehavior = new Quack();
flyBehavior = new FlyWithWings();
}
public void display(){
System.out.println("我是一只绿头鸭");
}
}
因为MallardDuck继承Duck类,所以具有flyBehavior与quackBehavior实例变量
绿头鸭使用Quack类处理呱呱叫,所以当performQuack()被调用时,叫的职责被委托给Quack对象,而我们获取到真正的呱呱叫。
使用FlyWithWing作为其FlyBehavior类型。
当MallardDuck类实例化时,它的构造器就会把继承来的quackBehavior实例变量初始化成Quack类型的新实例(Quack是QuackBehavior的具体实现类)
同样飞行的行为在它的构造器中由flyBehavior实例变量初始化为FlyWithWings类型的实例(FlyWithWings是FlyBehavior的具体实现类)
虽然说过不对具体实现编程,但是以上的代码在构造器中做了具体实现一个Quack实现类的实例。
虽然把行为设定成具体的类(通过实例化类似Quack或FlyWithWIngs的行为类,并把它指定到行为引用变量中),但是还是可以在运行时改变它。
虽然初始化实例变量的做法不够弹性,但是因为这个实例是接口类型的,在运行时能够用多态的方式给它动态地指定不同地实现类。即使还是有更好的方法。
http://c.biancheng.net/view/1390.html