软件构造(4-8讲,下半)
6. Object-Oriented Programming (OOP)
Interface接口
- 接口可以实现静态方法,使用 static 关键字。通过static关键字可以实现静态工厂方法,从而将接口的实现类封装,实现对外信息隐藏。
- 接口中也允许使用 default 关键字来定义并实现实例方法,这个应用有点类似于抽象类的功能。
- 通过default方法,在接口中统一实现某些功能,无需在各个类中重复实现它。
继承和重写
**严格继承:**子类只能添加新方法,无法重写超类中的方法。原因:父类中的方法使用了final关键字限定。
**重写:**子类的方法覆盖了父类的方法。重写的方法应该与父类方法有相同的签名,只有这样编译器才会判定为重写的方法。使用@Override annotation强制检查是否重写了超类中的方法。
在子类中如果想调用被重写的父类的方法,可以使用super.method()。
如果是在构造方法中调用父类的构造方法,则必须在构造方法的第一行调用super()。
多态
多态是继封装、继承之后,面向对象的第三大特性。
Java作为面向对象的语言,可以描述一个事物的多种形态。如Student类继承了Person类,那么一个Student的对象便既是Student,又是Person。多态体现为父类引用变量可以指向子类对象。前提条件:必须有子父类关系。
定义格式:父类类型 变量名=new 子类类型();
三种多态:特殊多态、参数化多态、子类型多态
3.多态体现为父类引用变量可以指向子类对象。
4.前提条件:必须有子父类关系。
特殊多态:功能重载
重载:多个方法具有同样的名字,但有不同的参数列表或返回值类型。参数列表必须不同,返回值类型、可见性、异常均为可以相同也可不同。重载不仅可以发生在类内,也可发生在父类与子类之间。
父类与子类之间发生重载的例子如下。这两个情况都不能编译成功,以为无论是a还是h,他们的运行时类型都是Animal,而不是通过new创建的具体类型。
class Animal{ public void eat(){...} } class Horse{ public void eat(){...} public void eat(String food){...} } //两种不能通过编译的情况 Animal a = new Animal(); a.eat("Apple"); Animal h = new Horse(); h.eat("Apple");
参数化多态:泛型
使用泛型参数代替具体的类型。作为一个泛型接口,当实现的时候可以实现一个具有具体类型的子类型,也可以实现一个具有泛型接口的实现类。泛型是jdk5才引进的,泛型其实指得就是参数化类型,使得代码可以适应多种类型。像容器,List< T >,大量使用了泛型,它的主要目的之一就是用来指定容器要持有什么类型的对象。
通配符
?
,只在使用泛型的时候出现,不能在定义中出现。子类型多态
终极目的:不同类型的对象可以统一处理而无需区分。
遵循的设计原则:LSP
LSP:子类可以扩展父类的功能,但不能改变父类原有的功能;子类可以实现父的抽象方法,但不能覆盖父类的非抽象方法。(下文还有)
理解:只要父类能出现的地方,子类就可以出现,并且替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道是父类还是子类。但反之,未要求。
7. 等价性equals()和==
等价性是基于等价关系的,满足自反、对称、传递三个性质,它的空间意义是:如果R中的多个值都对应于A中的同一个值,那么这些R值都应该是等价的。
引用等价性:使用==判断地址是否相同作为判断是否等价的依据。对基本数据类型,必须使用这种办法判断是否相等。
对象等价性:使用equals()方法判断两个对象是否相同法作为判断是否等价的依据,对于对象类型,使用这种办法来判断对象是否等价,如果只用==则是在判断两个对象的ID(内存里的同一空间)是否相等。
8.LSP
子类重写父类的方法应该满足的条件:
编译器在静态类型检查时强制满足的条件
子类型可以增加方法,但不可删除
子类型需要实现抽象类型中的所有未实现方法
子类型中重写的方法返回值必须与父类相同或符合co-variance(协变)
子类型中重写的方法必须使用同样类型的参数或者符合contra-variance(逆变)的参数
子类型中重写的方法不能抛出额外的异常还应该满足的条件
更强的不变量 (RI)
更弱的前置条件
更强的后置条件
9.协变
关于返回值的类型,应该保持不变或者变得更具体,也就是与派生的方向一致。
所抛出的异常的类型也是如此。
class T { Object a() { … } void b() throws Throwable {…} } class S extends T { @Override //返回值从Object协变成了String,这是符合重写的语法的 String a() { … } @Override //抛出的异常从Throwable协变成了IOException,这也是符合重写的语法的 void b() throws IOException {…} }
10. 逆变
关于参数的类型,应该保持不变或者变得更抽象,也就是与派生的方向相反。
class T { void c(String s) { … } } class S extends T { @Override //虽然按照LSP这是合法的,但是在java语法中,不当作override,而是overload void c(Object s) { … } }
11. 委派
1.Dependency:依赖关系,临时性的delegation。把被delegation的对象以参数方式传入。只有在需要的时候才建立与被委派类的联系,而当方法结束的时候这种关系也就随之断开了。
//如果要让鸭子用其他方式叫(或飞)只需更换new的q(f)的类型即可 Flyable f = new FlyWithWings(); //使用翅膀飞行的飞行方式 Quackable q = new Quack(); //鸭叫声的叫声 Duck d = new Duck(); //一只鸭子 d.fly(f); //让鸭子飞 d.quack(q); //让鸭子叫 class Duck { //no field to keep Flyable object public void fly(Flyable f) { f.fly(); } //让这个鸭子以f的方式飞 public void quack(Quackable q) { q.quack() }; //让鸭子以q的方式叫 }
2.Association:关联关系,永久性的delegation。被delegation的对象保存在rep中,该对象的类型被永久的与此ADT绑定在了一起。
//法一:在构造方法中传入参数绑定 Flyable f = new FlyWithWings(); Duck d = new Duck(f); d.fly(); class Duck { Flyable f; //这个必须由构造方法传入参数绑定 public Duck(Flyable f) { this.f = f; } public void fly(){ f.fly(); } } //法二:在rep或构造方法中直接写死 Duck d = new Duck(); d.fly(); class Duck { //这两种实现方式的效果是相同的 Flyable f = new FlyWithWings(); //写死在rep中 public Duck() { f = new FlyWithWings(); } //写死在构造方法中 public void fly(){ f.fly(); } }
3.Composition: 更强的association,但难以变化。也就是Association中的法二。
4.Aggregation: 更弱的association,可动态变化。也就是Association中的法一。
12.框架
- 黑盒框架:通过实现特定接口进行框架扩展,采用的是delegation机制达到这种目的,通常采用的设计模式是策略模式(Strategy)和观察者模式(Observer)
- 白盒框架:通过继承和重写实现功能的扩展,通常的设计模式是模板模式(Template Method)。
白盒框架所执行的是框架所写好的代码,只有通过override其方法来实现新的功能,客户端启动的的是第三方开发者派生的子类型。黑盒框架并不是这样,黑盒所预留的是一个接口,在框架中只调用接口中的方法,而接口中方法的实现就依据派生出的子类型的不同而不同,它的客户端启动的就是框架本身。
白盒:
public abstract class Application extends JFrame { //这些抽象方法就是为了实现不同的部分而设计的 abstract protected String getApplicationTitle(); abstract protected String getButtonText(); abstract protected String getInitialText(); abstract 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()); 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.setTitle(getApplicationTitle()); ... } } //使用 public class Calculator extends Application { //重写了框架中的四个抽象方法 protected String getApplicationTitle() { return "My Great Calculator"; } protected String getButtonText() { return "calculate"; } protected String getInititalText() { return "(10-3)*6"; } protected void buttonClicked() { JOptionPane.showMessageDialog(this, "the result of " + getInput() + "is" + calculate(getInput())); } private String calculate(String text) {...} }
黑盒:
public class Application extends JFrame { private JTextField textField; private Plugin plugin; public Application() { } protected void init(Plugin p) { p.setApplication(this); this.plugin = p; JPanel contentPane = new JPanel(new BorderLayout()); contentPane.setBorder(new BevelBorder(BevelBorder.LOWERED)); JButton button = new JButton(); button.setText(plugin != null ? plugin.getButtonText() : "ok"); contentPane.add(button, BorderLayout.EAST); textField = new JTextField(""); if (plugin != null) textField.setText(plugin.getInititalText()); textField.setPreferredSize(new Dimension(200, 20)); contentPane.add(textField, BorderLayout.WEST); if (plugin != null) button.addActionListener((e) -> { plugin.buttonClicked(); }); this.setContentPane(contentPane); ... } public String getInput() { return textField.getText(); } } public interface Plugin { public String getApplicationTitle(); public String getButtonText(); public String getInititalText(); public void buttonClicked(); public void setApplication(Application app); } //使用,实现Plugin接口 public class CalcPlugin implements Plugin { private Application app; public void setApplication(Application app) { this.app = app; } public String getButtonText() { return "calculate"; } public String getInititalText() { return "10/2+6"; } public void buttonClicked() { JOptionPane.showMessageDiaLog(null, "The result of" + application.getInput() + "is" + calculate(application.getInput())); } public String getApplicationTitle() { return "My Great Calculator"; } }