这周个人大学老师在给咱们讲UML建模时,说到了一个鸭子的设计模式,我以为上课太快了,感受没听懂,便在网上看到一个大神作出以下很详细的解释,我以为很是适合刚入门的同窗一块儿学习!html
列出以下:java
假设咱们须要设计出各类各样的鸭子,一边游泳戏水, 一边呱呱叫。很明显这时咱们须要设计了一个鸭子超类(Superclass),并让各类鸭子继承此超类。
public abstract class Duck {
public void Swim() {
//会游泳
}
public abstract display();//各类外观不同,因此为抽象
public void Quack() {
//会叫
}
}
每一只鸭子就继承Duck类
public class MallardDuck extends Duck {
public void display() {
// 外观是绿色的
}
}
public class RedheadDuck extends Duck{
public void display(){
// 外观是红色的
}
}
好了,咱们完成这些后,可是发现咱们须要有一些鸭子是会飞的,应该怎么修改呢?
也许你要说这很简单,在Duck类里面直接加入一个fly()方法,不就能够了。
public abstract class Duck {
public void Swim() {
//会游泳
}
public abstract display();//各类外观不同,因此为抽象
public void Quack() {
//会叫
}
public void fly(){
//会飞
}
}
这时你会发现全部的鸭子都变成了会飞的,很明显这是不对了,例如橡皮鸭显然就不是了。
你也许想到了另外一种方法,在会飞的鸭子类里才添加该方法不就能够了嘛,
public class MallardDuck extend Duck{
public void display(){
// 外观是绿色的
}
public void fly(){
//会飞
}
}
这个方法看起来是很不错,但是有不少种鸭子都会飞的时候,代码的复用性很明显是不够好的,你不得不在
每个会飞的鸭子类里去写上同一个fly()方法,这可不是个好主意.
可能你又想到另外一个方法:采用继承和覆盖,在Duck类里实现fly()方法,在子类里若是不会飞的就覆盖它
public abstract class Duck {
public void Swim() {
//会游泳
}
public abstract display();//各类外观不同,因此为抽象
public void Quack(){
//会叫
}
public void fly(){
//会飞
}
}
//橡皮鸭吱吱叫,不会飞
public class RubberDuck extend Duck{
public void quack(){
//覆盖成吱吱叫
}
public void display{
//外观是橡皮鸭
}
public void fly{
//什么也不作
}
}
这样咱们真实现了确实能飞的鸭子才能够飞起来了,看起来主意不错!问题到这儿彷佛获得了解决
但咱们如今有了一种新的鸭子,诱铒鸭(不会飞也不会叫),看来须要这样来写
public class DecoyDuck extend Duck{
public void quack(){
//覆盖,变成什么也不作
}
public void display(){
//诱饵鸭
}
public void fly(){
//覆盖,变成什么也不作
}
}
每当有新的鸭子子类出现或者鸭子新的特性出现,就不得不被迫在Duck类里添加并在全部子类里检查可能须要覆盖fly()和quark()...这简直是无穷尽的恶梦。因此,咱们须要一个更清晰的方法,让某些(而不是所有)鸭子类型可飞或可叫。让鸭子的特性能有更好的扩展性。
用一下接口的方式把fly()取出来,放进一个Flyable接口中,这样只有会飞的鸭子才实现这个接口,固然咱们也能够照此来设计一个Quackbable接口,由于不是全部的鸭子都会叫,也只让会叫的鸭子才去实现这个接口.
但这个方法和上面提到的在子类里去实现fly同样笨,若是几十种均可以飞,你得在几十个鸭子里去写上同样的fly(),若是一旦这个fly有所变动,你将不得不找到这几十个鸭子去一个一个改它们的fly()方法。
由于改变鸭子的行为会影响全部种类的鸭子,而这并不恰当。Flyable与Quackable接口一开始彷佛还挺不错, 解决了问题( 只有会飞的鸭子才继承Flyable) , 可是Java的接口不具备实现代码, 因此继承接口没法达到代码的复用。这意味着:不管什么时候你须要修改某个行为,你必须得往下追踪并修改每个定义此行为的类。
策略模式的第一原则:找出应用中可能须要变化之处,把它们独立出来,不要和那些不须要变化的代码混在一块儿。 好吧,回头看一下这个Duck类,就咱们目前所知,除了fly()和quack()的问题以外,Duck类还算一切正常,主要是鸭子的行为老是可能变化的,让咱们头痛就在于这些行为的变化,那咱们就把这些行为独立出来。
为了要把这两个行为从Duck 类中分开, 咱们将把它们自Duck 类中取出,创建一组新类表明每一个行为。咱们创建两组类(彻底远离Duck类),一个是「fly」相关的,一个是「quack」相关的,每一组类将实现各自 的动做。比方说,咱们可能有一个类实现「呱呱叫」,另外一个类实现「吱吱叫」,另外一个类实现「安静」。咱们利用接口表明每组行为,比方说, FlyBehavior来表明飞的行为,QuackBehavior表明叫的行为,而让每一种行为具体类来实现该行为接口。
在此,咱们有两个接口,FlyBehavior和QuackBehavior,还有它们对应的类,负责实现具体的行为:
public interface FlyBehavior {
public void fly();
}
public class FlyWithWings implements FlyBehavior{
public void fly{}{
//实现鸭子飞行
}
}
public class FlyNoWay implements FlyBehavior{
public void fly{}{
//什么也不作,不会飞
}
}
public interface QuackBehavior{
public void quack();
}
public class Quack implements QuackBehavior{
public void quack(){
//实现鸭子呱呱叫
}
}
public class Squeak implements QuackBehavior{
public void quack(){
//实现鸭子吱吱叫
}
}
public class MuteQuack implements QuackBehavior{
public void quack(){
//什么也不作,不会叫
}
}
实际上这样的设计,咱们已经可让飞行和呱呱叫的动做被其余的对象复用,由于这些行为已经与鸭子类无关了。若是咱们新增一些行为,也不会影响到既有的行为类,也不会影响有已经使用到飞行行为的鸭子类。
好了,咱们设计好鸭子的易于变化的行为部分后,该到了整合鸭子行为的时候了。
这时咱们该想到策略模式的另外一个原则了:
针对接口编程,而不是针对实现编程。
首先, 在鸭子中加入两个实例变量,分别为「flyBehavior」与「quackBehavior」,声明为接口类型( 而不是具体类实现类型), 每一个变量会利用多态的方式在运行时引用正确的行为类型( 例如:FlyWithWings 、Squeak...等)。咱们也必须将Duck类与其全部子类中的fly()与quack()移除,由于这些行为已经被搬移到FlyBehavior与 Quackehavior类中了,用performFly()和performQuack()取代Duck类中的fly()与quack()。
public abstract class Duck(){
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public void swim(){
//会游泳
}
public abstract void display();//各类外观不同,因此为抽象
public void performQuack(){
quackBehavior.quack();
}
public void performFly(){
flyBehavior.fly();
}
}
很容易,是吧?想进行呱呱叫的动做,Duck对象只要叫quackBehavior对象
去呱呱叫就能够了。在这部分的代码中,咱们不在意QuackBehavior 接口的对象究竟是什么,咱们只关心该对象
知道如何进行呱呱叫就够了。
好吧! 如今来关心如何设定flyBehavior 与quackBehavior的实例变量。
看看MallardDuck类:
public class MallardDuck extends Duck {
public MallardDuck() {
\\绿头鸭使用Quack类处理呱呱叫,因此当performQuack() 被调用,就把责任委托给Quack对象进行真正的呱呱叫。
quackBehavior = new Quack();
\\使用FlyWithWings做为其FlyBehavior类型。
flyBehavior = new FlyWithWings();
}
}
因此,绿头鸭会真的『呱呱叫』,而不是『吱吱叫』,或『叫不出声』。这是怎么办到的?当MallardDuck实例化时,它的构造器会
把继承来的quackBehavior实例变量初始化成Quack类型的新实例(Quack是QuackBehavior的具体实现类)。一样的处理方式也能够用在飞行行为上: MallardDuck 的构造器将flyBehavior 实例变量初始化成FlyWithWings 类型的实例(
FlyWithWings是FlyBehavior的具体实现类)。
输入下面的Duck类(Duck.java) 以及MallardDuck 类MallardDuck.java),并编译之。
public abstract class Duck {
//为行为接口类型声明两个引用变量, 全部鸭子子类(在同一个packge)都继承它们。
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public Duck() {
}
public abstract void display();
public void performFly() {
flyBehavior.fly();//委托给行为类
}
public void performQuack() {
quackBehavior.quack();//委托给行为类
}
public void swim() {
System.out.println("All ducksfloat, even decoys!");
}
}
public class MallardDuck extends Duck {
public MallardDuck() {
quackBehavior = newQuack();
flyBehavior = newFlyWithWings();
}
public void display() {
System.out.println("I’m a real Mallard duck");
}
}
输入FlyBehavior接口(FlyBehavior.java)与两个行为实现类(FlyWithWings.java与FlyNoWay.java),并编译之。
public interface FlyBehavior {//全部飞行行为类必须实现的接口
public void fly();
}
public class FlyWithWings implements FlyBehavior {//这是飞行行为的实现, 给「真会」飞的鸭子用 .. .
public void fly() {
System.out.println("I’m flying!!");
}
}
public class FlyNoWay implements FlyBehavior {//这是飞行行为的实现, 给「不会」飞的鸭子用( 包括橡皮鸭和诱饵鸭)
public void fly() {
System.out.println("I can’t fly");
}
}
输入QuackBehavior接口(QuackBehavior.java)及其三个实现类(Quack.java、MuteQuack.java、Squeak.java),并编译之。
public interface QuackBehavior {
public void quack();
}
public class Quack implements QuackBehavior {
public void quack() {
System.out.println(“Quack”);
}
}
public class MuteQuack implements QuackBehavior {
public void quack() {
System.out.println(“<< Silence >>”);
}
}
public class Squeak implements QuackBehavior {
public void quack() {
System.out.println(“Squeak”);
}
}
输入并编译测试类(MiniDuckSimulator.java)
public class MiniDuckSimulator {
public static void main(String[] args) {
Duck mallard = new MallardDuck();
mallard.display();
//这会调用MallardDuck继承来的performQuack() ,进而委托给该对象的QuackBehavior对象处理。(也就是说,调用继承来的quackBehavior的quack())
mallard.performQuack();
//至于performFly() ,也是同样的道理。
mallard.performFly();
}
}
运行结果:
I’m a real Mallard duck
Quack
I’m flying!!
虽然咱们把行为设定成具体的类(经过实例化相似Quack 或FlyWithWings的行为类, 并指定到行为引
用变量中),可是仍是能够在运行时轻易地改变该行为。
因此,目前的做法仍是颇有弹性的,只是初始化实例变量的做法不够弹性罢了。
咱们但愿一切能有弹性,毕竟,正是由于一开始的设计的鸭子行为没有弹性,才让咱们走到如今这条路。
咱们还想可以「指定」行为到鸭子的实例, 比方说, 想要产生绿头鸭实例,并指定特定「类型」的飞行
行为给它。干脆顺便让鸭子的行为能够动态地改变好了。换句话说,咱们应该在鸭子类中包含设定行为的方法。
由于quackBehavior实例变量是一个接口类型,因此咱们是可以在运行时,透过多态动态地指定不一样的QuickBehavior实现类给它。
咱们在鸭子子类透过设定方法(settermethod)设定鸭子的行为,而不是在鸭子的构造器内实例化。
在Duck类中,加入两个新方法:今后之后,咱们能够「随时」调用这两个方法改变鸭子的行为。
public strate class Duck(){
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public void setFlyBehavior(FlyBehavior fb) {
flyBehavior = fb;
}
public void setQuackBehavior(QuackBehavior qb) {
quackBehavior = qb;
}
}
好了,让咱们再制造一个新的鸭子类型:模型鸭(ModelDuck.java)
public class ModelDuck extends Duck {
public ModelDuck() {
flyBehavior = new FlyNoWay();//初始状态,咱们的模型鸭是不会飞的。
quackBehavior = new Quack();//初始状态,咱们的模型鸭是能够叫的.
}
public void display() {
System.out.println("I’m a modelduck");
}
}
创建一个新的FlyBehavior类型(FlyRocketPowered.java)
public class FlyRocketPowered implements FlyBehavior {
// 咱们创建一个利用火箭动力的飞行行为。
public void fly() {
System.out.println("I’m flying with arocket!");
}
}
改变测试类(MiniDuckSimulator.java),加上模型鸭,并使模型鸭具备火箭动力。
public class MiniDuckSimulator {
public static void main(String[] args) {
Duck mallard = new MallardDuck();
mallard.performQuack();
mallard.performFly();
Duck model = new ModelDuck();
//第一次调用performFly() 会被委托给flyBehavior对象(也就是FlyNoWay对象),该对象是在模型鸭构造器中设置的。
model.performFly();
//这会调用继承来的setter 方法,把火箭动力飞行的行为设定到模型鸭中。哇咧! 模型鸭忽然具备火箭动力飞行能力。
model.setFlyBehavior(new FlyRocketPowered());
//若是成功了, 就意味着模型鸭动态地改变行为。若是把行为的实现绑死在鸭子类中, 可就没法作到这样。
model.performFly();
}
}
运行一下,看下结果
I’m a real Mallard duck
Quack
I’m flying!!
I’m a model duck
I can’t fly
I’m flying with a rocket!
如同本例通常,当你将两个类结合起来使用,这就是组合(composition)。这种做法和『继承』不一样的地方在于,
鸭子的行为不是继承而来,而是和适当的行为对象『组合』而来。
这是一个很重要的技巧。实际上是使用了策略模式中的第三个设计原则, 多用组合,少用继承。
如今来总结一下,鸭子的行为被放在分开的类中,此类专门提供某行为的实现。
这样,鸭子类就再也不须要知道行为的实现细节。
鸭子类不会负责实现Flyable与Quackable接口,反而是由其余类专门实现FlyBehavior与QuackBehavior,
这就称为「行为」类。由行为类实现行为接口,而不是由Duck类实现行为接口。
这样的做法迥异于以往,行为再也不是由继承Duck超类的具体实现而来, 或是继承某个接口并由子类自行实现而来。
(这两种做法都是依赖于「实现」, 咱们被实现绑得死死的, 没办法更改行为,除非写更多代码)。
在咱们的新设计中, 鸭子的子类使用接口( FlyBehavior与QuackBehavior)所表示的行为,因此实际的实现不会被
绑死在鸭子的子类中。( 换句话说, 特定的实现代码位于实现FlyBehavior与QuakcBehavior的特定类中),这样咱们就得到了更大的灵活性和可扩展性。地址:https://zhidao.baidu.com/question/1447672451450665060.html