大三党修炼(自动狗头)花点时间了解JAVA中的一些设计模式,写博客是为了帮自己理清思路,希望也能帮到大家来理解设计模式,在学习设计模式前但我知道设计模式对于框架是一种优化,框架并不包含那种设计模式,我也没接触过框架,我希望后面接触框架后能和设计模式进行更多的思考。笔记中会存在疏漏,希望大家多多指出。
策略模式定义了算法族,分别封装起来,让他们之间可以互换,此模式让算法的变化独立与使用算法的客户。
1. 问题引入
开发一套成功的模拟鸭子游戏:SimUDuck,游戏中会出现各种各样的鸭子,可以游泳,呱呱叫等多种行为,拥有OO思想。
2.思想的迭代更新
(1)最开始看到问题大家一定会想到直接先定义Duck父类,然后在父类中写方法类来实现鸭子的行为。
abstract class Duck {
/*
抽象类的使用原则如下:
(1)抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public;
(2)抽象类不能直接实例化,需要依靠子类采用向上转型的方式处理;
(3)抽象类必须有子类,使用extends继承,一个子类只能继承一个抽象类;
(4)子类(如果不是抽象类)则必须重写抽象类之中的全部抽象方法(如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。);
*/
public void quack(){
//呱呱叫
System.out.println("呱呱呱");
}
public void swim(){
//游泳
System.out.println("游泳");
}
public abstract void display();//鸭子的外表(因为每一种鸭子的外观都不同,所以该方法是抽象的)要在子类中实现此方法
}
如果我现在要引入两种鸭子:红头鸭,绿头鸭。
public class MallarDuck extends Duck{
/*
绿头鸭重写抽象类的display方法
*/
@Override
public void display() {
System.out.println("头顶一片绿");
}
}
class RedheadDuck extends Duck{
@Override
public void display() {
System.out.println("樱木花道鸭");
}
}
现在需求来了客户需要鸭子飞起来,那也很简单对不对在Duck中咋加一个fly()方法,创建的子类只要调用就好了。多简单!然而事实不是如此,有些鸭子是不会飞的如橡皮鸭(即某些不适合该行为的子类也具备了该行为),那我重写父类方法不就好了。
class RubberDuck extends Duck{
@Override
public void display() {
System.out.println("橡皮");
}
@Override
public void quack() {
System.out.println("吱吱");
}
@Override
public void fly() {
System.out.println("我飞不了");
}
}
但是如果我加入更多类似的玩具鸭他不会飞也不会叫就得又要开始重写这几个方法。所以继承的思想不适合处理需求多变的情况。
(2)转换思路利用接口如何?
可以把fly()从Duck中取出来放入Flyable接口中,以此类推取出Quack变成接口Quackable等其他变化多的方法。
public interface Flyable {
//飞行接口类
/*
1)接口不能有构造方法,抽象类可以有。
2)接口不能有方法体,抽象类可以有。
3)接口不能有静态方法,抽象类可以有。
4)在接口中凡是变量必须是public static final,而在抽象类中没有要求。
*/
abstract public void fly();
}
public interface Quackable {
abstract public void quack();
}
有了接口后我们通过子类来实现接口类,这时候橡皮鸭,玩具鸭之类的依据自己的需求来选择接口实现,如橡皮鸭不会飞我就不去实现这个接口。
class RubberDuck extends Duck implements Quackable {
@Override
public void display() {
System.out.println("橡皮");
}
@Override
public void quack() {
System.out.println("吱吱");
}
}
但是新的问题又出现了重复的代码又会变多了例如之前的绿头鸭,后头鸭要实现两个接口重写接口中的抽象方法就会使得代码重复增加
public class MallarDuck extends Duck implements Flyable,Quackable{
/*
绿头鸭重写抽象类的display方法
*/
@Override
public void display() {
System.out.println("头顶一片绿");
}
public void fly(){
System.out.println("飞");
}
@Override
public void quack() {
System.out.println("嘎嘎噶");
}
}
class RedheadDuck extends Duck implements Flyable,Quackable{
@Override
public void display() {
System.out.println("樱木花道鸭");
}
@Override
public void fly() {
System.out.println("飞");
}
@Override
public void quack() {
System.out.println("嘎嘎");
}
}
例如飞行和叫声一样,如果多个类似会飞会叫的就会不断地重复代码。(手动狗头:终于理解了软件工程中前期是多么重要。。。。)
(3)采用良好的OO设计原则
我们现在的代码要解决的是用一种既有的代码影响最小的方式来修改软件,花更少的时间,而更多的让程序去做更cool的事
!!!!背下来
设计原则1:找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
现在我要把飞行和呱呱叫取出变成行为类,如何设计?
设计原则2:针对接口编程,而不是针对实现编程。
所以我们利用接口来代表每个行为,如将飞行类接口有不会飞和会飞两种实现类实现,如下.
public interface FlyBehavior {
public void fly();
}
接着我们写会飞和不会飞的实现类
public class FlyNoWay implements FlyBehavior {
@Override
public void fly() {
System.out.println("我不会飞");
}
}
public class FlyWithWing implements FlyBehavior {
@Override
public void fly() {
System.out.println("飞飞飞飞");
}
}
还有瓜瓜叫的行为和多种叫声实现类
public interface QuackBehavior {
abstract public void quack();
}
public class NutQuack implements QuackBehavior {
public void quack() {
System.out.println("不会叫");
}
}
public class Quack implements QuackBehavior {
@Override
public void quack() {
System.out.println("呱呱呱");
}
}
public class Squeak implements QuackBehavior {
@Override
public void quack() {
System.out.println("吱吱吱");
}
}
那到这大家也和我有一样的疑问那我们该如何调用实现接口呢?
答案是多态用到的同样是子类型重写父类新但和我们最初的多态在形式上有 点不一样,在于其多态是在Duck的子类对象中调用转型。(个人理解)我们首先来看如何改造Duck父类
public abstract class Duck {
//行为接口类型声明俩引用变量,所有鸭子子类(在同一个package中)都继承他们
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public Duck(){
}
public void swim(){
//游泳
System.out.println("游泳");
}
public abstract void display();//鸭子的外表(因为每一种鸭子的外观都不同,所以该方法是抽象的)要在子类中实现此方法
//委托给行为类
public void performFly(){
flyBehavior.fly();
}
public void performQuack(){
quackBehavior.quack();
}
}
这时我们来写子类中的构造方法,因为绿头鸭使用Quack类处理,所以当Duck的performQuack();呱呱叫的职责被委托给Quack对象,而我们得到了真正地呱呱呱。(这段有点难懂 ,我之前再写构造函数时写的public void MallarDuck导致程序出错说flybehavior为null,出错之后我理解了是那句话会导致程序认为他是方法,没有给所谓的被委托对象赋值。)
public class MallarDuck extends Duck {
public MallarDuck(){
//构造方法
quackBehavior=new Quack();
flyBehavior=new FlyWithWing();
}
/*
绿头鸭重写抽象类的display方法
*/
@Override
public void display() {
System.out.println("头顶一片绿");
}
}
我们再来试着写一下橡皮鸭子类,明确一下橡皮鸭会吱吱叫,但不会飞,则实现方法是FlyNoWay()和Squeak();我们只需要吧quackBehavior,flyBehavior
的赋值即可
class RubberDuck extends Duck {
public RubberDuck(){
flyBehavior=new FlyNoWay();
quackBehavior=new Squeak();
}
@Override
public void display() {
System.out.println("橡皮");
}
}
那我们该如何在主函数调用呢?
public class test {
public static void main(String[] args) {
Duck mallard=new MallarDuck();
mallard.display();
mallard.performQuack();
mallard.performFly();
Duck rubber=new RubberDuck();
rubber.display();
rubber.performFly();
rubber.performQuack();
}
}
我们现在来看一下这个调用过程那橡皮鸭的行为来说我们首先像多态一样创建对象——橡皮鸭,利用Duck调用(ps:作为小白的我前面说过抽象对象不能实例化,这里很巧妙他用的对象是子类缺用父类调用,涨知识了!!!)
话说回来这时候就会在构造函数中赋对应的方法:
flyBehavior=new FlyNoWay();
quackBehavior=new Squeak();
主函数中rubber.performFly()进入父类Duck中的performFly()方法里的flyBehavior.fly();而flyBehavior是飞行行为接口类对象的变量类型,fly()就是调用接口方法,这里用到了多态就会去吱吱叫和不会飞的实现方法类。
结果如下:
吼吼吼这样我们的方法完美解决了过多的重写重复的代码(第一个设计方法我前前后后看了三遍才看懂泪目。。。)
在子类中的构造方法我们可以改成设定方法放在Duck中
public void setFlyBehavior(FlyBehavior fb){
flyBehavior=fb;
}
主函数就可以写成
Duck mallard=new MallarDuck();
mallard.setsetFlyBehavior(new FlyWithWing());
2.总结
1.在该中客户使用封装好的飞行和呱呱叫的算法族,其中每个行为看作一个算法族。
2.鸭子行为不是继承而来而是和适当的对象组合而来。
3.组合建立的系统具有强大的弹性,不仅可以将算法族封装成类,更可以“在运行时动态的改变行为”。