设计模式——面向接口编程之柔性多态增强代码的可扩展性

引言

多态和封装、继承一起作为面向对象的三大特性,无论是Java还是其他面向对象的语言,相信很多人谈起多态可能都不会陌生,绝大多数都可以说出多态的很多知识,可往往在现实项目的开发过程中,这最基本的特性,由于种种原因,常常被人遗忘或者懒得使用,要知道我们编码并不是仅仅追求完成任务,假如说后期的维护升级还是要你来做的话你就会深刻感受到了,笔者最近对接了一位同事的项目深有感触,同时这也是深刻理解设计模式的必修课,所以总结下。

一、多态概述

多态和封装、继承一起作为面向对象的三大特性,无论是Java还是其他面向对象的语言,灵活使用好多态,对于提高我们的编码质量尤其重要,首先多态体现了动态绑定(dynamic binding),即在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法,这就提供了很大的灵活性,同时也利于消除类型之间的耦合度。

1、多态的定义

指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。(发送消息即函数调用),简而言之就是同样的方法名在不同的对象中可以实现不同的功能

2、多态的条件

  • 要有继承或者实现接口
  • 重写,即子类重写或者覆盖父类的方法
  • 子承父业,即定义父类的引用用子类来赋值初始化

3、多态的实现方式

接口实现,继承父类进行方法重写,同一个类中进行方法重载,在使用的时候使用不同的子类去初始化父类。

二、多态的应用

假如我们有以下的需求:求根据输入的值求长方形、圆形的面积,其中计算长方形面积时要求输入长和宽,计算圆形时只需要半径即可,接下来逐步分享下以多态的思想来解这个题。

首先,所谓面向对象即把一切看成对象,很明显长方形、圆各自可以抽象成为一个Java对象,然后输入值和计算面积可以抽象为方法,首先把他们共性的功能抽象为接口

public interface IShape {
    void input();
    float getArea();
}

此时我们已经定义了个形状接口,一般来说我们只要分别定义长方形类和圆形类并实现这个IShape接口即可,但是封装性还是不高,因为无论是求哪个形状的面积都得先输入再计算,这是一个流程,于是封装了一个流程类

public class ShapeProcess {
    private IShape ishape;
    public ShapeProcess(IShape shape){
        this.ishape=shape;
    }
    
    public float computeArea(){
        ishape.input();
        return ishape.getArea();
    }
}

如此就构建了一个小框架模型,其他业务也可以直接套用。

套用这个模型来解决我们前面的那个问题,只需要根据不同形状实现IShape接口,

public class Rectangle implements IShape {
    private float width;
    private float height;
    
    public float getWidth() {
        return width;
    }
    public void setWidth(float width) {
        this.width = width;
    }
    public float getHeight() {
        return height;
    }
    public void setHeight(float height) {
        this.height = height;
    }

    @Override
    public void input() {
        System.out.println("请一次输入长、宽:");
        Scanner s=new Scanner(System.in);
        setHeight(s.nextFloat());
        setWidth(s.nextFloat());
        s.close();
    }

    @Override
    public float getArea() {
        return width*height;
    }
}
public class Circle implements IShape {
    private float radius;
    
    public float getRadius() {
        return radius;
    }
    public void setRadius(float radius) {
        this.radius = radius;
    }

    @Override
    public void input() {
        System.out.println("请输入半径:");
        Scanner s=new Scanner(System.in);
        setRadius(s.nextFloat());
        s.close();
    }

    @Override
    public float getArea() {
        return (float) (Math.PI*radius*radius);
    }
}

然后调用ShapeProcess 的computeArea计算面积即可。

public class TestDuotai {
    public static void getRectangleArea(){
        IShape shape=new Rectangle();
        ShapeProcess process=new ShapeProcess(shape);
        System.out.println("长方形面积"+process.computeArea());
    }
    
    public static void getCircleArea(){
        IShape shape=new Circle();
        ShapeProcess process=new ShapeProcess(shape);
        process.computeArea();
        System.out.println("圆形面积"+process.computeArea());
    }
}

这样设计的好处在于代码的源头来自IShape接口,而ShapeProcess控制了流程,无论是计算什么形状的面积,我们要做的只是需要实现IShape接口定义具体的形状类即可,好好想下是不是很多地方都是可以套用的,但是扩展性还欠缺火候。

过了一段时间,客户突然要求要计算周长

一般想法重新定义IShape接口,增加一个获取周长的方法,这样做的后果就是前面的实现模块程序都需要修改重新编译,这明显扩展性不好,造成这样后果的根本原因是父类、子类定义的多态方法耦合度过高。,而柔性多态可以降低耦合,实现也很简单,重新设计下IShape2接口

public interface IShape2 {
    /**
     * 业务多态方法,根据tag 来执行不同的分支方法
     * @param tag 业务标号
     * @return
     */
    public Object compute(int tag);
}

对应的Circle2


public class Circle2 implements IShape2 {
    private float radius;
    
    public Circle2(float radius) {
        this.radius = radius;
    }

    @Override
    public Object compute(int tag) {
        Object result=null;
        switch(tag){
        case 0:
            getArea();//计算面积
            break;
        case 1:
            getPerimeter();//计算周长
            break;
            default:break;
        }
        return result;
    }
    //非多态方法
    Object getArea(){
        float area=(float) (Math.PI*radius*radius);
        return new Float(area);
    }
    //非多态方法
    Object getPerimeter(){
        float area=(float) (Math.PI*radius*2);
        return new Float(area);
    }
}

简单计算圆的面积和周长

public static void getCircle2Area(){
        IShape2 shape2=new Circle2(8.0F);
        Float result=(Float) shape2.compute(0);
        System.out.println("圆形面积:"+result);
    }
    
    public static void getCircle2Peimeter(){
        IShape2 shape2=new Circle2(8.0F);
        Float result=(Float) shape2.compute(1);
        System.out.println("圆形周长:"+result);
    }

所谓的柔性思想其实很简单,就是接口定义多态方法与各子类的普通具体方法之间的关系是间接的,而非直接的,这就消弱了父类与子类多态方法的强关联,简而言之,子类通过重写多态派发方法

显而易见通过tag来完成相应的功能,但是所传递的tag似乎不受控制,更完善一点的话接口可以设计为

public interface IShape3 {
    /**
     * 业务多态方法,根据tag 来执行不同的分支方法
     * @param tag 业务标号
     * @return
     */
    public Object compute(int tag);
    public int getBizTag(String bizName);//通过业务名获取对应的tag编号
}

完善后的Circle3为

public class Circle3 implements IShape3 {
    private static List<String> bizList=new ArrayList();
    static{
        bizList.add("getArea");
        bizList.add("getPerimeter");
    }
    private float radius;
    
    public Circle3(float radius) {
        this.radius = radius;
    }
    
    @Override
    public int getBizTag(String bizName) {
        return bizList.indexOf(bizName);
    }

    @Override
    public Object compute(int tag) {
        Object result=null;
        switch(tag){
        case 0:
            getArea();//计算面积
            break;
        case 1:
            getPerimeter();//计算周长
            break;
            default:break;
        }
        return result;
    }
    //非多态方法
    Object getArea(){
        float area=(float) (Math.PI*radius*radius);
        return new Float(area);
    }
    //非多态方法
    Object getPerimeter(){
        float area=(float) (Math.PI*radius*2);
        return new Float(area);
    }
}

完善后的简单应用

public static void getCircle3Area(){
        IShape3 shape3=new Circle3(8.0F);
        int tag=shape3.getBizTag("getArea");//如果需要计算周长则把计算周长的方法名穿过去就可以了
        Float result=(Float) shape3.compute(tag);
        System.out.println("圆形面积:"+result);
    }

这样设计的好处就是当有新需求的时候我们不需要修改接口模块,只需要去在具体类里添加一个case即可,每种模式都不是完美的,程序开发不宜盲目迷信去套用模式,至于如何设计应该结合实际,这篇文章只是分享多态的思想。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,560评论 18 399
  • 设计模式汇总 一、基础知识 1. 设计模式概述 定义:设计模式(Design Pattern)是一套被反复使用、多...
    MinoyJet阅读 3,894评论 1 15
  • 写在之前 因为简书字数限制,完整版地址:https://www.zybuluo.com/hainingwyx/no...
    hainingwyx阅读 13,860评论 0 41
  • 2017-04-10 一直流传着“一万小时定律”,只要你坚持一万小时某项技能的练习,你就能掌握此项技能! 然而事实...
    一粟于海阅读 320评论 0 1
  • 编者语: 铁凝有次拜访冰心,被问及“你有男朋友了吗?”之后随意回了一句“还没找呢。”90岁的冰心老人则语重心长地说...
    我是长今阅读 801评论 4 4