OOP:封装,继承,多态
松耦合,高内聚
例子:模拟鸭子的应用
参考资料:java鸭子全代码 鸭子类型 编程 - 变形虫 (bxcqd.com)
第一种方法:直接采用继承解决
设计了一个鸭子超类(Super Class),并让各种鸭子继承此超类。
public abstract class Duck {
public void Swim(){
// 会游泳
}
public void Quack(){
//会叫
}
public void Fly(){
//会飞
}
public void display(){
//外观
}
}
每一只鸭子就继承Duck类
public class MallardDuck extends Duck{
public void display(){
System.out.println("外观是绿色的");
}
public void fly(){
System.out.println("会飞");
}
}
但是我们发现不是每种鸭子都会飞,才用继承和覆盖,在Duck类里面实现fly方法,如果子类不会飞就覆盖它。
public class RubbrtDuck extends Duck{
public void quack(){
//覆盖成吱吱叫
}
public void display(){
//外观是橡皮鸭
}
public void fly(){
//什么都不用做
}
}
这样实现了能飞的鸭子才可以飞,但是现在又有一种鸭子,不会飞也不会叫
public class DecoyDuck extends Duck{
public void quack(){
//覆盖,变成什么也不做
}
public void display(){
//诱饵鸭
}
public void fly(){
//覆盖,变成什么也不做
}
}
可是每当新的鸭子子类出现或者鸭子新的特性出现,就不得不在Duck类添加所有子类里检查可能需要覆盖fly和quark。所以我们需要一个清晰的方法,让某些鸭子类可飞可叫,让鸭子的特性有更好的拓展性。
第二种方法:采用接口方式接口
用接口把方法fly取出来放进Flyable接口中,这样只有会飞的鸭子才实现这个接口,这样只有会飞的鸭子才实现这个接口,按照此来设计一个Quackable接口。
但这种方法非常笨!如果几十种鸭子都可以飞,就需要在每个种类的鸭子里写上一样的fly方法,一旦fly有所改变(例如:用翅膀飞变成用引擎飞),就需要一个个更改fly方法。改变鸭子的行为会影响所有种类的鸭子。
public abstract class Duck {
public void siwm(){
//会游泳
}
}
public interface Flyable {
public void fly();
}
public class MallardDuck extends Duck implements Flyable{
public void fly() {
//会飞
}
}
public class RubbrtDuck extends Duck{
//橡皮鸭不会飞,所以不继承Flyable接口
}
}
第三种方法:策略模式
策略模式第一原则:找出应用中可能需要变化的地方把它们独立出来,不要和哪些不需要变化的代码混在一起。回看Duck类,总是可能改变的就是fly和quack,那就把这两种方法独立出来。
为了把这两个行为从Duck类中分开,我们把他们取出建立一组新类代表每个行为,分别和fly和quack相关,每一组实现各自的动作,例如FlyBehavior代表飞的行为,从而让每一种行为的具体类来实现该行为接口。
public interface FlyBehavior {
public void fly();
}
public class FlyWithWings implements FlyBehavior{
@Override
public void fly() {
//实现鸭子飞行
}
}
public class FlyNoWay implements FlyBehavior{
@Override
public void fly() {
//什么都不做,不会飞
}
}
经过这样设计,已经可以让飞行和叫的动作被其他对象复用。因为这些行为已经和鸭子类无关了。新增一些行为也不会影响到既有的行为类和已经使用飞行行为的鸭子。
设计好鸭子易于变化的行为部分后,该到了整合鸭子行为的时候了。这是运用到策略模式的另一个原则:针对接口编程,而不是针对实现编程
首先在鸭子中加入两个实例变量,分别为flyBehavior和quackBehavior,声明为接口类型而不是具体类实现类型,每个变量会利用多态的方式与其所有子类中的fly和quack移除,因为这些行为已被搬移到接口类中了。用performFly()和performQuack()取代Duck类中的fly和quack。
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public void Swim(){
// 会游泳
}
public void display(){
//外观
}
public void performFly(){
flyBehavior.fly();//委托给行为类
}
public void performQuack(){
quackBehavior.quack();
}
}
想要鸭子进行呱呱叫的动作,Duck对象只要用quackBehavior对象去呱呱叫就可以了。在这部分代码中,我们不在乎QuackBehavior接口的对象到底是什么,只关心和知道如何呱呱叫就可以了。
现在来设定flyBehavior的实例变量。
public class MallardDuck extends Duck{
public MallardDuck(){
//绿头鸭子使用Quack类处理呱呱叫,所以当performQuack()被调用,
//就吧责任委托给Quack对象进行真正的呱呱叫
quackBehavior = new Quack();
//使用FlyWithWings作为其FlyBehavior的类型
flyBehavior = new FlyNoWay();
}
}
所以绿头鸭真的会呱呱叫而不是其他叫声,这是为什么呢?
当 MallardDuck实例化时,它的构造器会把继承来的quackBehavior实例变量初始化为Quack类型的新实例。
最后编译测试类
public class MiniDuckSimulator {
public static void main(String[] args) {
Duck mallard = new MallardDuck();
mallard.display();
//此时调用MallardDuck继承来的performQuack,
//进而委托给该对象的QuackBehavior的对象处理
//也就是说,调用了继承来的quackBehavior的quack
mallard.performQuack();
mallard.performFly();
}
}
后续完善:能够指定行为到鸭子的实例。比如,想要产生绿头鸭实例,并指定特点类型的飞行给它。让鸭子的行为可以动态地改变就好了。换句话说,应该在鸭子类中包含设定行为的方法。
因为quackBehavior实例变量是一个接口类型,所以我们要在能够运行时,通过多态来动态地指定不同的QuackBehavior实现类给它。
在鸭子类中通过设定方法(settermethod)设定鸭子的行为,而不是在鸭子的构造期内实例化。在Duck中加入两个新方法:从此以后,我们可以随时调用这两个方法改变鸭子的行为。
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public void setFlyBehavior(FlyBehavior fb){
flyBehavior=fb;
}
public void setQuackBehavior(QuackBehavior qb) {
quackBehavior = qb;
}
public void Swim(){
// 会游泳
}
public void display(){
//外观
}
}
public class ModelDuck extends Duck{
public ModelDuck(){
flyBehavior = new FlyNoWay();
//初始状态模型鸭不会飞
quackBehavior = new Quack();
//初始状态的模型鸭可以叫
}
public void display(){
System.out.println("我是一只模型鸭");
}
}