软件构造学习笔记第九章——面向复用的软件构造技术

目录

1.软件复用的角度和类型

1.1角度

1.2类型

2.LSP(Liskov Substitution Principle)

2.0协变/逆变

2.0.1协变

2.0.2逆变

2.1具体内容

2.1.1可静态检测部分

2.1.2不可静态检测部分

2.2例子

2.3番外篇——关于泛型以及一些易错的地方

2.3.1一些易错的地方

2.3.2关于泛型的一些知识

3.源代码复用

3.1做法

3.1存在问题

4.模块(类/接口)复用

4.1类的复用

4.1.1继承(已说过)

4.1.2委托

4.2接口的复用

5.系统级复用

5.1库的复用

5.2框架的复用

5.2.1概念

5.2.2无框架

5.2.3白盒框架

5.2.4黑盒框架


1.软件复用的角度和类型

1.1角度

①面向复用编程:开发可复用的软件。

②基于复用编程:利用已有的可复用软件搭建应用系统。

1.2类型

①白盒复用:源代码可见,可修改和扩展。复制已有代码到正在开发的系统进行修改,可定制程度高,对其修改增加了软件的复杂度,且需要对源代码有充分了解。

②黑盒复用:源代码不可见,不能修改。只能通过API接口来使用,无法修改代码。简单清晰但适应性差。

2.LSP(Liskov Substitution Principle)

2.0协变/逆变

2.0.1协变

        即子类型某些方面变得比父类型更具体,则称子类型该方面发生协变。

2.0.2逆变

         即子类型某些方面变得比父类型更抽象,则称子类型该方面发生逆变。

注:java不支持子类型逆变。

2.1具体内容

观察一个子类型是否符合LSP,从三方面观察——RI、规约、异常。

2.1.1可静态检测部分

①子类型可以增加方法,但是不可以删去父类型的方法。

②子类型需要实现抽象类型中所有未实现的方法。

③子类型重写的方法的返回值类型必须和父类型相同或者比父类型更具体(协变)。

④子类型重写的方法传入的参数类型必须和父类型相同或者比父类型更抽象(逆变)。

⑤子类型重写的方法不能抛出额外的异常(协变)。

2.1.2不可静态检测部分

①子类型的RI不能弱于父类型

②子类型的前置条件不能强于父类型

③子类型的后置条件不能弱于父类型

2.2例子

//父类型
abstract class Vehicle
{
    int speed,limit;
    //@ invariant speed < limit;

    //@ requires speed != 0;
    //@ ensures speed < \old(speed);
    void brake();
}
//子类型
class Car extends Vehicle
{
    int fuel;
    boolean engineOn;
    //@ invariant speed < limit;
    //@ invariant fuel >= 0;比父类型更强

    //新增方法
    //@ requires fuel > 0 && !engineOn;
    //@ ensures engineOn;
    void start() { … }
    void accelerate() { … }

    //@ requires speed != 0;
    //@ ensures speed < \old(speed)
    void brake() { … }
}

2.3番外篇——关于泛型以及一些易错的地方

2.3.1一些易错的地方

//注意理解变量:声明一个Number数组型变量,变量名为numbers,变量引用堆上两个Number型的变量。
Number[] numbers = new Number[2];

//在堆上new一个Integer数据对象,Number型变量numbers[0]指向该对象
numbers[0] = new Integer(10);

//在堆上new一个Double数据对象,Number型变量numbers[1]指向该对象
numbers[1] = new Double(3.14);

//声明一个Integer数组型变量myInts,变量元素分别指向堆上的1、2、3、4
Integer[] myInts = {1,2,3,4};

//向上造型,将myInts的Integer类型当成Number型看待,实际上myNumber还是指向堆上Integer型变量
Number[] myNumber = myInts;

//由于该对象是指向Integer的,因此发生错误
myNumber[0] = 3.14; //run-time error!

2.3.2关于泛型的一些知识

(1)

① ArrayList<String>是List<String>的子类型,因为'<>'中的String相同,且ArrayList是List的子类型。

②List<String>不是List<Object>的子类型,此时java把它视为两种不同的类型,一个是盛放String类型的容器,一个盛放Object类型的容器,两者相互独立。

List<Integer> myInts = new ArrayList<Integer>();
myInts.add(1);
myInts.add(2);
List<Number> myNums = myInts; //compiler error,因为myInts和myNums独立,不存在父子关系
myNums.add(3.14);

(2) 通配符

        通配符'?'常用于泛型内部的方法不依赖于传入的对象类型。且可以通过<? super A>和<? extends A>来限制传入对象只能是A的父类或者A的子类。

3.源代码复用

3.1做法

        即复制粘贴代码到自己的程序上来。

3.1存在问题

        比如如果该代码有一处需要修改,那么被复制的副本都要修改...还有其它问题不一一列举。

4.模块(类/接口)复用

4.1类的复用

4.1.1继承(已说过)

4.1.2委托

        委托:一个对象使用另一(多)个对象已经实现了的方法。

(1)委托的形式

Ⅰ.依赖——暂时性的委托

        如果对象A想使用对象B的方法,通过在对象A的方法传参传入对象B来完成对对象B方法的调用叫做依赖。

//类B
//Flyable是一个接口,该接口下有各种对'fly'的不同实现,其中FlyWithWings表示用翅膀飞
class FlyWithWings implements Flyable
{ 
    public void fly() 
    {
        System.out.println("fly with wings");
    }
}    
//类A
class Duck 
{
    //no field to keep Flyable object
    void fly(Flyable f) //传入对象B
    {
        f.fly();
    }
}
//客户调用
Flyable f = new FlyWithWings();//客户可以根据f的选择来让Duck实现不同的fly方式
Duck d = new Duck();
d.fly(f);

Ⅱ.关联=(组合+聚合)——永久性的委托

        如果对象A想使用对象B的方法,通过在对象A内定义对象B的属性,来使用B的方法叫做关联。

①组合 (紧绑定)

//类A
class Duck 
{
    //定义类B的属性,且明确指向了FlyWithWings,因此只能使用FlyWithWings里的fly
    Flyable f = new FlyWithWings();    
    void fly()
    {
        f.fly();
    }
}
//客户使用
Duck d = new Duck();
d.fly();

②聚合(松绑定)

class Duck 
{
    Flyable f;//没有指明具体fly的类型
 
    void Duck(Flyable f) 
    { 
        this.f = f; 
    }
    //客户可以根据想要飞的类型需要传入不同的类型来进行设置
    void setFlyBehavior(Flyable f) 
    {
        this.f = f;
    }

    void fly()
    { 
        f.fly();
    }
}
//客户使用
Flyable f = new FlyWithWings();
Duck d = new Duck(f);//初始化d用翅膀飞
d.fly();
d.setFlyBehavior(new FlyWithOthers());//更改d用FlyWithOthers类型飞
d.fly();

(2)委托的应用

        试想有一群能叫会飞的动物,假设叫的方式有三种A、B、C,飞的方式也有三种X、Y、Z,动物a会的是A和X,动物b会的也是A和X,此时很容易想到要建一个父类,父类实现A和X,然后a和b去继承父类即可,但是若此时出现了动物c会的是A和Y,动物d会的是B和X呢,若挨个这样建父类,那最多有3*3=9种父类。此时可以使用委托,建立一个“叫”接口,“叫”接口下有A、B、C三种实现,建立一个“飞”接口,“飞”接口下有X、Y、Z三种实现。然后对于任意叫和飞组合的动物都可以用委托来实现了。

//叫的接口
interface Barkable
{
    public void bark();
}
//飞的接口
interface Flyable
{
    public void fly();
}
//能飞会叫动物的接口
interface Animal extends Barkable,Flyable {}
//实现A叫
public class Abark implements Barkable
{
    @override
    public void bark(){...}
}
//实现B叫
public class Bbark implements Barkable
{
    @override
    public void bark(){...}
}
//实现C叫
public class Cbark implements Barkable
{
    @override
    public void bark(){...}
}
//实现X飞
public class Xfly implements Flyable
{
    @override
    public void fly(){...}
}
//实现Y飞
public class Yfly implements Flyable
{
    @override
    public void fly(){...}
}
//实现Z飞
public class Zfly implements Flyable
{
    @override
    public void fly(){...}
}
//实现委托的动物
public class RealAnimal implents Animal
{
    Flyable flyBehavior;//用接口定义,实现子类型多态
    Barkable barkBehavior;//用接口定义,实现子类型多态
    
    void setFlyBehavior(Flyable f) 
    {
        this.flyBehavior = f; 
    }
    void setBarkBehavior(Barkable q)
    {
        this.barkBehavior = q; 
    }
    @override
    public void fly()
    {
        this.flyBehavior.fly();
    }
    @override
    public void bark()
    {
        this.barkBehavior.bark();
    }
}
//客户使用
RealAnimal animal = new RealAnimal();//生成一个能飞会叫的动物
Flyable f = new Xfly();//X飞
Barkable q = new Abark();//A叫
animal.setFlyBehavior(f);//让动物实现X飞
animal.setBarkBehavior(q);//让动物实现A叫
animal.fly();
animal.bark();

4.2接口的复用

5.系统级复用

5.1库的复用

        库就是可供复用的类和方法的集合。

5.2框架的复用

5.2.1概念

        框架就是给定了一个固定的待填充的代码块,我们可以去根据自己的需要来填充和使用这个代码块。

public abstract class Application extends JFrame 
{
    //这是个伪框架,用于对框架概念有个大概了解
    //Application里的代码除了按需填的东西之外都不可以被更改
    public Application()
    {
        JPanel contentPane = new JPanel(new BorderLayout());
        contentPane.setBorder(new BevelBorder(BevelBorder.LOWERED));
        JButton button = new JButton();
        button.setText(getButtonText());//getButtonText是按需填的东西
        contentPane.add(button, BorderLayout.EAST);
        textField = new JTextField("");
        textField.setText(getInitialText());//按需填的东西
        textField.setPreferredSize(new Dimension(200,20));
        contentPane.add(textField,BorderLayout.WEST);
        button.addActionListener((e)->{buttonClicked();});//按需填的东西
        this.setContentPane(contentPane);this.pack();
        this.setLocation(100,100);
        this.setTitle(getApplicationTitle());//按需填的东西
    }
}

5.2.2无框架

public class Calculator extends JFrame 
{
    //无框架例子
    JTextField textField;
    //由于都是具体的,因此无法复用
    public Calculator()
    {
        JPanel contentPane = new JPanel(new BorderLayout());
        contentPane.setBorder(new BevelBorder(BevelBorder.LOWERED));
        JButton button = new JButton();
        button.setText("calculate");//框架下getButtonText是按需填的东西,非框架则是具体的
        contentPane.add(button, BorderLayout.EAST);
        textField = new JTextField("");
        textField.setText("10/2+6");//具体的
        textField.setPreferredSize(new Dimension(200,20));
        contentPane.add(textField,BorderLayout.WEST);
        button.addActionListener(/*calculation code*/);//具体的
        this.setContentPane(contentPane);this.pack();
        this.setLocation(100,100);
        this.setTitle("My great Calculator");//具体的
    }
}

5.2.3白盒框架

特点:

①通过继承和重写来进行扩展

②设计模式:模板模式

③白盒框架在(抽象)父类中,子类通过继承来实现调用框架

public abstract class Application extends JFrame 
{
    //这是个白盒框架
    //子类按需重写下列方法,然后在框架Application中进行调用
    protected String getApplicationTitle(){return "";}
    protected String getButtonText(){return "";}
    protected String getInitialText(){return  "";}
    protected void buttonClicked(){}
    private JTextField textField;

    public Application()
    {
        JPanel contentPane = new JPanel(new BorderLayout());
        contentPane.setBorder(new BevelBorder(BevelBorder.LOWERED));
        JButton button = new JButton();
        button.setText(getButtonText());//由子类重写geetButtonText填充,下同
        contentPane.add(button, BorderLayout.EAST);
        textField = new JTextField("");
        textField.setText(getInitialText());//按需填的东西
        textField.setPreferredSize(new Dimension(200,20));
        contentPane.add(textField,BorderLayout.WEST);
        button.addActionListener((e)->{buttonClicked();});//按需填的东西
        this.setContentPane(contentPane);this.pack();
        this.setLocation(100,100);
        this.setTitle(getApplicationTitle());//按需填的东西
    }
}
//客户调用框架
//通过继承和重写方法利用框架实现计算器Calculator——可与之前无框架的Calculator对比
//之所以说是白盒,是因为父类方法客户可见,客户通过知道父类方法的名字才能重写该方法
//客户重写方法
public class Calculator extends Application 
{
    //重写方法
    protected String getApplicationTitle(){return "My Great Calculator";}
    protected String getButtonText(){return "calculate";}
    protected String getInitialText(){return  "(10-3)*6";}
    protected void buttonClicked(){JOptionPane.showMessageDialog(this,"The result of"+getInput()+" is " + calculate(getInput());}
}
//客户调用
Calculator mycalculator = new Calculator();

5.2.4黑盒框架

 特点:

①通过接口和委派的方法进行扩展

②设计模式:策略模式和观察者模式

③通过API的模式完成

//黑盒框架,客户不可见
//之所以说是黑盒,是因为父类客户不可见,客户只能通过接口来自定义接口,然后调用框架
public class Application extends JFrame
{
    private JTextField textField;
    private Plugin plugin;//接口
    protected void init(Plugin p)
    {
        p.setApplication(this);
        this.plugin = p;
        ...
    }
}
//接口,客户可见
public interface Plugin
{
    String getApplicationTitle();
    String getButtonText();
    String getInititalText();
    void buttonClicked();
    void setApplication(Application app);
}
//客户使用框架
//客户自定义接口
public class CalcPlugin implements Plugin
{
    private Application app;
    public void setApplication(Application app)
    {
        this.app = app;
    }
    public String getButtonText()
    {
        return "calculate";
    }
    public String getInitialText()
    {
        return "10/2 + 6";
    }
    public void buttonClicked(){...}    
}
//客户通过自定义的接口调用框架
Application myapplication = new Application();
myapplication.init(new CalcPlugin());

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值