目录
2.LSP(Liskov Substitution Principle)
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());