大量结构、功能相近的冗余的代码不仅带来了维护上的额外成本,而且使代码变得不可读。更好的代码重用,使程序代码短小精炼才能体现手艺的价值。可重用设计的几大措施:1. 设计公共函数库;2. 使用面向对象编程的多态、继承、接口实现等技术特性;3. 使用设计模式,设计模式是对反复出现的一般问题的解决方法;4. 使用软件框架、组件和模板;5. 使用面向过程编程,比如高阶函数。

公共函数库

重用设计的一个最常见形式是公共函数库。许多通用的操作,比如在已知的格式之间进行数据转换,访问外部数据源,访问外部接口,或者操作数字、字符、名字、地址、日期等信息,都可以被提炼到公共函数库中。使用公共函数库,可以避免“重复造轮子”,同时,经过严苛单元测试的公共函数可以保证质量。缺点是,无法调节细节,这些细节会影响程序性能和期望的输出。对于获得、学习和配置公共函数的各项成本很难优化。

公共函数库的形式可以是本地JAR、静态类、DLL,也可以是一些远程服务,比如RPC、REST、SOA。

面向对象编程

面向对象技术通过方法、消息、类、继承、封装、多态和实例等机制构造软件系统,并为软件重用提供强有力的支持。Java有三种基本的方式支持进行代码重用,分别是接口实现、扩展抽象类和扩展类。

接口实现 (Interface implements)

一个类通过关键字implements声明自己使用一个或者多个接口, 接口的方法是空的, 必须重写才能使用。对于依据接口标准调用的类来说就很方便,一次写成,到处调用。

扩展抽象类(Extends abstract class)

使用抽象方法的类,(继承抽象类)通过抽象类中的非抽象方法提供代码重用,通过其中的抽象方法为不同的子类提供灵活性。

扩展类(extends class)

在类的声明中,通过关键字extends来创建一个类的子类,继承之后可以使用父类的方法,也可以重写父类的方法。JAVA中不支持多重继承,但是可以用接口 来实现。

从一定角度来看,封装和继承几乎都是为多态而准备的。这是我们最后一个概念,也是最重要的知识点。

什么是多态?

指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。(发送消息就是函数调用)

实现多态的技术称为?

动态绑定(dynamic binding),是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。

多态的作用?
消除类型之间的耦合关系。

现实中,关于多态的例子不胜枚举,比方说按下 F1 键这个动作,如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档;如果当前在 Word 下弹出的就是 Word 帮助;在 Windows 下弹出的就是 Windows 帮助和支持。同一个事件发生在不同的对象上会产生不同的结果。

多态存在的三个必要条件,要求大家做梦时都能背出来

  1. 要有继承
  2. 要有重写
  3. 父类引用指向子类对象

多态的好处

  1. 可替换性(substitutability)。多态对已存在代码具有可替换性。例如,多态对圆Circle类工作,对其他任何圆形几何体,如圆环,也同样工作。
  2. 可扩充性(extensibility)。多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。例如,在实现了圆锥、半圆锥以及半球体的多态基础上,很容易增添球体类的多态性。
  3. 接口性(interface-ability)。多态是超类通过方法签名,向子类提供了一个共同接口,由子类来完善或者覆盖它而实现的。如超类Shape规定了两个实现多态的接口方法,computeArea()以及computeVolume()。子类,如Circle和Sphere为了实现多态,完善或者覆盖这两个接口方法。
  4. 灵活性(flexibility)。它在应用中体现了灵活多样的操作,提高了使用效率。
  5. 简化性(simplicity)。多态简化对应用软件的代码编写和修改过程,尤其在处理大量对象的运算和操作时,这个特点尤为突出和重要。

基于继承实现的多态

package polymorphism;

class Dance {
    public void play(){
        System.out.println("Dance.play");
    }
    public void play(int i){
        System.out.println("Dance.play" + i);
    }
}

class Latin extends Dance {
    public void play(){
        System.out.println("Latin.play");
    }
    public void play(char c){
        System.out.println("Latin.play" + c);
    }
}
class Jazz extends Dance {
    public void play(){
        System.out.println("Jazz.play");
    }
    public void play(double d){
        System.out.println("Jazz.play" + d);
    }
}
public class Test {
    public void perform(Dance dance){
        dance.play();
    }
    public static void main(String[] args){
        new Test().perform(new Latin()); // Upcasting
    }
}

执行结果:Latin.play。这个时候你可能会发现perform()方法里面并没有类似“if 参数类型 = Dance/Latin”这样的判断,其实这就是多态的特性,它消除了类型之间的耦合关系,令我们可以把一个对象不当做它所属的特定类型来对待,而是当做其基类的类型来对待。因为上面的Test代码完全可以这么写:

public class Test {
    public void perform(Latin dance){
        dance.play();
    }
    public void perform(Jazz dance){
        dance.play();
    }
    public static void main(String[] args){
        new Test().perform(new Latin()); // Upcasting
    }
}

但是这样你会发现,如果增加更多新的类似perform()或者自Dance导出的新类,就会增加大量的工作,而通过比较就会知道第一处代码会占优势,这正是多态的优点所在,它改善了代码的组织结构和可读性,同时保证了可扩展性。

那么到底JVM是怎么指向Latin类中play()的呢?为了解决这个问题,JAVA使用了后期绑定的概念。当向对象发送消息时,在编译阶段,编译器只保证被调用方法的存在,并对调用参数和返回类型进行检查,但是并不知道将被执行的确切代码,被调用的代码直到运行时才能确定。拿上面代码为例,JAVA在执行后期绑定时,JVM会从方法区中的Latin方法表中取到Latin对象的直接地址,这就是真正执行结果为什么是Latin.play的原因,

基于接口实现的多态

//定义接口InterDance  
interface c  
{  
    void dance();  
}  

//实现接口InterA的类B  
class Jazz implements InterDance  

{  
    public void dance() {      
        System.out.println(“This is Jazz.”);  
    }  
}  

//实现接口InterA的类C  
class Latin implements InterDance  {  
    public void dance()  {      
        System.out.println(“This is Latin.”);  
    }  
} 

class Test  {  
    public static void main(String[] args)  {  
        InterDance dance;  
        dance= new Jazz();  
        a.dance();   
        a = new Latin();   
        a.dance();   
    }  
}

输出结果为:

This is Jazz

This is Latin

上例中类Jazz和类Latin是实现接口InterDance的两个类,分别实现了接口的方法dance(),通过将类B和类C的实例赋给接口引用a而实现了方法在运行时的动态绑定,充分利用了“一个接口,多个方法”展示了Java的动态多态性。

改写类的实例

通过类继承实现代码重用不是精确的代码重用技术,因此它并不是最理想的代码重用机制。换句话说,如果不继承整个类的所有方法和数据成员,我们无法重用该类里面的单个方法。继承总是带来一些多余的方法和数据成员,它们总是使得重用类里面某个方法的代码复杂化。另外,派生类对父类的依赖关系也使得代码进一步复杂化:对父类的改动可能影响子类;修改父类或者子类中的任意一个类时,我们很难记得哪一个方法被子类覆盖、哪一个方法没有被子类覆盖;最后,子类中的覆盖方法是否要调用父类中的对应方法有时并不显而易见。任何方法,只要它执行的是某个单一概念的任务,就其本身而言,它就应该是首选的可重用代码。为了重用这种代码,我们必须回归到面向过程的编程模式,把类的实例方法移出成为全局性的过程。为了提高这种过程的可重用性,过程代码应该象静态工具方法一样编写:它只能使用自己的输入参数,只能调用其他全局性的过程,不能使用任何非局部的变量。这种对外部依赖关系的限制简化了过程的应用,使得过程能够方便地用于任何地方。当然,由于这种组织方式总是使得代码具有更清晰的结构,即使是不考虑重用性的代码也同样能够从中获益。在Java中,方法不能脱离类而单独存在。为此,我们可以把相关的过程组织成为独立的类,并把这些过程定义为公用静态方法。

对于下例中有需要对CreditCard类进行扩展的需求,但是CreditCard中存在过多冗余属性和方法。

class CreditCard {
    ...
    public void validate() {...} 
    public void pay() {...} 
    public boolean refund(Amount p) {...}
}

于是我们不打算采用继承的手段,而是将方法进行改写:

public void validate() {return ANewCard.validate(this);} 
public void pay() {return ANewCard.pay(this);} 
public boolean refund(Amount p) {return ANewCard.refund(this, p);}

Card对象定义如下:

class ANewCard{ 
    static public void validate(CreditCard creditcard) {…} 
    static public void pay(CreditCard creditcard) {…} 
    static public boolean refund(CreditCard creditcard,Amount p) {…} 
}

这样一来我们可以在Card对象中纳入原来的实现逻辑并自行进行方法定义而不需要全部继承完整的CreditCard类。

但是这样做会带来一个负面影响:Card类的扩展性并不好。由于Card类是静态类,其中所有的方法实现一旦复杂后就会面临越来越臃肿的问题。

把参数类型改成接口

在措施二的基础上,我们可以进一步对原来的参数改写接口来达到更好的扩展性:

interface ANewCard { 
    public void ANewCardInfo() {…} 
    public void getCheckInfo {…} 
    ...
}
//将原来的传入参数改为接口类型
class Card { 
    static public void validate(ANewCard creditcard) {…} 
    static public void pay(ANewCard creditcard) {…} 
    static public boolean refund(ANewCard creditcard, Amount p) {…} 
}

完成上面的改造后,我们就有了对CreaditCard进行进一步扩展的可能性,接下来的事就交给Java的多态来完成。

设计模式

设计模式是对反复出现的问题的一般解决方案。面向对象设计模式通常以类别或对象来描述其中的关系和相互作用,但不涉及用来完成应用程序的特定类别或对象。设计模式能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免会引起麻烦的紧耦合,以增强软件设计面对并适应变化的能力。

并非所有的软件模式都是设计模式,设计模式特指软件“设计”层次上的问题。还有其他非设计模式的模式,如架构模式。同时,算法不能算是一种设计模式,因为算法主要是用来解决计算上的问题,而非设计上的问题。

《设计模式》一书原先把设计模式分为创建型模式、结构型模式、行为型模式,把它们通过授权、聚合、诊断的概念来描述。除了这三种模式外,还有并发型模式。

创建型模式

*点击模式名称查看详细内容及例子

模式名称 描述
生成器模式(Builder) 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
工厂方式模式(Factory method) 定义一个接口用于创建对象,但是让子类决定初始化哪个类。工厂方法把一个类的初始化下放到子类。
原型模式(Prototype) 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。可以提高性能并将内存占用率降至最低。
单例模式(Singleton) 确保一个类只有一个实例,并提供对该实例的全局访问。

结构型模式

*点击模式名称查看详细内容及例子

模式名称 描述
适配器模式(Adapter) 将某个类的接口转换成客户端期望的另一个接口表示。适配器模式可以消除由于接口不匹配所造成的类兼容性问题。
外观模式(Facade) 为子系统中的一组接口提供一个一致的界面, 外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
代理模式(Proxy) 为其他对象提供一个代理以控制对这个对象的访问。

行为型模式

*点击模式名称查看详细内容及例子

模式名称 描述
责任链模式(Chain of responsibility) 为解除请求的发送者和接收者之间耦合,而使多个对象都有机会处理这个请求。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它。
迭代器模式(Iterator) 提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露该对象的内部表示。
访问者(Visitor) 封装一些施加于某种数据结构元素之上的操作。一旦这些操作需要修改,接受这个操作的数据结构可以保持不变。访问者模式适用于数据结构相对未定的系统,它把数据结构和作用于结构上的操作之间的耦合解脱开,使得操作集合可以相对自由的演化。
观察者模式(Observeror) 在对象间定义一个一对多的联系性,由此当一个对象改变了状态,所有其他相关的对象会被通知并且自动刷新。

框架

软件框架,通常指的是为了实现某个业界标准或完成特定基本任务的“软件组件规范”“软件产品”。框架的功能类似于基础设施,与具体的软件应用无关,但是提供并实现最为基础的软件架构和体系。

开发人员通常使用“开源平台GITHUB”上的第三方程序或框架,最大限度的去重用软件。虽然软件框架通常是用于特定领域,或适用于特定程序。

开发中心常见的框架诸如:SPRING、MYBATIS、NETTY、DSF等等。而CTP则是包含了IDE以及各种插件在内的开发平台,不属于软件框架范畴。

高阶函数

高阶函数是至少满足下列一个条件的函数:1、接受一个或多个函数作为输入;2、输出一个函数。在函数式编程中,高阶函数可以在许多情况下使用,使用情况包括在已有的设计模式或软件框架中使用。

在无类型lambda演算,所有函数都是高阶的;在有类型lambda演算中,高阶函数一般是那些函数型别包含多于一个箭头的函数。在很多函数式编程语言中能找到的map函数是高阶函数的一个例子。它接受一个函数Function作为参数,并返回接受一个列表并应用Function到它的每个元素的一个函数。高阶函数的其他例子包括函数复合、积分和常量函数。

函数式编程的核心在高阶函数与偏函数,在JAVA 8中,提供了很多函数式的接口,但跟Python、Javascript的函数相比较,还是存在较大的距离。首先看一个高阶函数的例子,这里利用局部变量域特性,进行延迟求值,如下:

/**
 * 输入一定数量的参数,然后统一求值
 * @param size 需要求值的个数
 * @param fn 求值函数
 * @return 函数对象
 * 从函数的定义就可以看出,Java函数编程的内在思想还是面向对象
 */
public IntFunction<Integer> addNum(int size, ToIntFunction<List<Integer>> fn) {
    //  声明局部变量,用于存储传入参数
    final List<Integer> args = Lists.newArrayList();
    return new IntFunction<Integer>() {

        @Override
        public Integer apply(int value) {
            //  没有达到定义的数量之前,不求值
            int result = -1;
            if(args.size() == size) {
                result = fn.applyAsInt(args);
            } else {
                args.add(value);
            }
            //  返回结果
            return result;
        }

    };
}
//  准备测试对象
IntFunction<Integer> addFun = this.addNum(3, items -> {
    //  利用reduce进行求值
    return items.stream().reduce(0, (x, y) -> x + y);
});

//  方法调用还很生硬,有个莫名其妙的函数名apply,可能会引起业务的误解
addFun.apply(1);
addFun.apply(2);
addFun.apply(3);

//  超过了数量不求值
int result = addFun.apply(4);
//  1+2+3 = 6
assertThat(result, IsEqual.equalTo(6));

JAVA是一门非常优秀的面向对象语言,在函数式编程方面,跟其他函数语言相比,还是显得非常笨重与生涩,并且其内在体现出来的思想,依旧式面向对象,更重要的式,对函数式编程支持的特性较少,例如实现柯里化就非常困难。

组件

组件(component)技术是各种软件重用方法中最重要的一种方法,也是分布式计算和Web服务的基础。网络应用中的软件组件,又被称为中间件(middleware)。

组件技术的应用现在已经十分广泛,从Windows编程中使用的各种控件和公用对话框,到ActiveX控件和DirectX的应用;从微软公司的COM,到Sun公司的JavaBean。其中最流行的组件技术的应用是——客户端的VBX(微软/VB)和服务器端的EJB(Sun/Java)。

在网络及其应用都很发达的今天,对组件服务的需求十分强烈,因此组件技术近年来得到了飞速的发展和广泛的应用。

模板

模板相当于工业生产中所用的“模具”。有各种各样的模板(如函数模板,模块模板等),利用这些模板可以比较快速地建立对应的软件产品。模板把不变的封装在内部,对可能变化的部分提供了通用接口,由使用者来对这些接口进行设定或实现。

results matching ""

    No results matching ""