目录
临近考试最近复习了一下课程后面的内容,也看明白了很多之前上课的时候没看明白的知识,就在这里分享一下,都是个人的理解,希望能帮助到这里学习有一点点困难的同学,也欢迎大家批评指正错误。
注:这里只写一些我上课时没太听懂的内容,不是知识点的总结。
1.Liskov可替换原则
在说明这个Liskov可替换原则之前,我们需要先介绍一下什么是行为子类型
行为子类型就是说如果我们有一个变量x,它的类型为T,方法p(x)存在且能根据x得出我们想要的结果,那么对于一个变量y,它的类型是T的子类型S,那么p(y)也依然能根据y得到我们想要的结果,我这么说你可能还是会不太理解,没关系,让我们来举个例子。
看如下关系,Cat是Animal的子类
Animal a=new Animal();
Animal b1=new Cat();
Cat b2=new Cat();
行为子类型就是说在可以使用a的场景下,使用b1和b2代替a,不会对我们想要的结果产生任何影响。a=b1;a=b2;
那么我们现在就能对Liskov可替换原则进行一个定义,其实它就是一种强化的行为子类型,有着以下的条件:
1.前置条件不能强化(pre-condition)
2.后置条件不能弱化(post-condition)
3.不变量要保持
4.子类型方法参数:逆变(就是子类型的方法参数和父类型一样或者比父类型更宽泛),不过java不支持和父类型方法参数不同的方法重写
5.子类型方法返回值:协变(就是子类型的方法返回值和父类型一样或者更具体)
6.异常类型:协变(可以不抛出异常,或者抛出和父类型一样或更具体的异常)
也就是说规约更强。
举个例子
我们有一个叫做矩形的父类,和一个叫做正方形的子类,有计算面积的方法,父类的该方法pre-condition是长 l 和宽 w 都大于0,子类的是l和w大于0,且l=w,那么此时,正方形子类就不满足Liskov可替换原则,因为它的前置条件比父类更强。
一个小Tips
这里复习的时候发现了很神奇的事,我们都是知道Object是所有类型的父类,但是呢,我们来看下面的奇妙事情
List<Object>和List<String> 先别往下看,思考一下,究竟List<String>是不是List<Object>的子类呢?
答案是:不是
大家千万要记住这件事请,不然有时候传参数是进去的呦。
2.委托(delegation)
委托,原理很简单,就是我们不用那种继承的关系,而是直接在我们的类中直接或者间接的使用其他类的方法来完成某些功能。
也许到这里你还是不是很明白,别着急,我们往下看
委托有两大种形式:
1.Dependency 是一种临时性的委托,类与类之间的关系是 A类 use B类
2.Association 是一种永久性的委托,类与类之间的关系是 A类 has/owns B类
然后还有两种形式,我们可以将他们看成是Association的两种不同形式
2.1 Composition 强Association (在后面的示例对其做出具体解释)
2,2 Aggregation 弱Association(在后面的示例对其做出具体解释)
好,现在让我们来看例子
一些大前提
//用于委托的接口
public interface Flyable{
public void fly();
}
//实现接口的类
public class FlyWithWings{
public void fly(){
System.out.println("fly with wings");
}
}
Dependency实现形式,不用在类中引入委托类的属性,只是在某个/些方法中当成传入参数使用
public class Duck{
//仅仅当做fly方法的传入参数,将这个方法的实现委托给Flyable完成
void fly(Flyable f){
f.fly();
}
}
客户端代码就可以这样来使用
Flyable f=new FlyWithWings();
Duck d=new Duck();
d.fly(f);
然后我们就知道Association就是在类中要含有委托类的属性,那我们来看Composition和Aggregation
Composition就是没有构造器或者方法来给这个属性从外部传入新值。也就是说,这个属性是类内部赋予的,不能依靠外部传参来赋予或更改。
还是直观的用代码给出
public class Duck{
//直接有初值,且无法改变
Flyable f= new FlyWithWings();
public void fly(){
f.fly();
}
}
//或者我们用构造器来给予它一个无法改变的固定初值
public class Duck{
Flyable f;
public Duck(){
this.f=new FlyWithWings(); //直接有初值,且无法改变
}
//注意,初值不是外部传入的,是类自己规定的
public void fly(){
f.fly();
}
}
Aggregation,就是可以动态变化了,可以依靠外部赋值改变相应属性
给出代码示例
public class Duck{
Flyable f;
public Duck(Flyable f){
this.f=f; //可以外部赋值
}
//可以改变属性的方法,mutator
public setFlyBehavior(Flyable f){
this.f=f;
}
public void fly(){
f.fly();
}
}
总结:其实委托就是依靠其他类的方法来实现我们这个类里面的方法,这样减少了代码重复。
然后就是白盒框架和黑盒框架了,他们最主要的区别就是白盒框架用的是继承,黑盒框架用的是委托。
3.创造模式(Creational patterns)
这里只有一个模式,我们称之为工厂方法模式(Factory Method pattern),就是说我们将创造器的功能交给别的类或接口来实现,从而隐藏了我们具体使用的子类。
还是用代码来直观的给大家呈现出来
不用工厂方法时:
public interface Trace{
……
public void p();
}//接口
//实现接口的两个类
public class S_Trace implements Trace{
……
public void p(){
具体实现……
}
}
public class F_Trace implements Trace{
……
public void p(){
具体实现……
}
}
当我们要使用他们时,我们会这样来写我们的代码
Trace a1=new S_Trace();
a1.p();
//or
Trace a2=new F_Trace();
a2.p();
我们可以看到,这个时候我们是具体的知道我们用的是哪一个子类的。
然后,让我们创造工厂方法模式
public intface TraceFactory{
public Trace getTrace();
public Trace getTrace(String type);
……
}//工厂创造器
//实现工厂的类
public Factory implements TraceFactory{
public Trace getTrace(){
return new S_Trace();
}
public Trace getTrace(String type){
if(type.equals("s"))
return new S_Trace();
else if (type.equals("f"))
return new T_Trace();
else
……
}
}
这样的话我们调用创造器的时候就可以这样办了
Trace a1=new Factory().getTrace();
a1.p();
//or
Trace a2=new Factory().getTrace("f");
a2.p();
我们也可以用静态方法来实现,这里就不展示了。
4.结构模式(Structural patterns)
4.1 适配器模式(Adapter)
这个模式其实就是要为了遵循尽量不修改之前代码的原则,即开闭原则而诞生的。
倘若我们有一个LR类,来面有一个dispiay方法,要求我们输入坐标系中一个矩形的最左下点(x , y)和他的沿x轴方向长度w,及沿y轴方向的长度h,来构造一个矩形。
public class LR{
……
public void display(int x,int y,int w,int h){
具体实现
}
}
但是如果我们现在要求输入的是最左下点(x1,y1)和最右上点(x2,y2)来构造我们的矩形,那么我们显然不能用LR类中的dispiay方法了,怎么办呢,这时候就需要我们的适配器了。
//接口
public interface Shape{
void display(int x1,int y1,int x2,int y2);
}
//适配器来实现这个接口
public class R implements Shape{
void display(int x1,int y1,int x2,int y2){
new LR().dispiay(x1,y1,x2-x1,y2-y1);
}
}
这样我们的代码就可以这样写啦
Shape myR= new R();
myR.dispiay(x1,y1,x2,y2);
4.2装饰器模式(Decorator)
这个模式是比较难理解的,就是对现有类的一个包装,然后我们可以通过这个装饰器为其添加功能但不改变原有的代码结构。不仅用到了委托,也用到了继承
装饰器有四个组件
(1)最初的抽象组件(例如抽象接口),这里定义了方法的规范
(2)被装饰者,就是实现接口的基础类
(3)装饰器组件,定义装饰着型为规范,持有最初的抽象组件的实例引用
(4)具体装饰,实现装饰器
我们还是通过代码演示一下吧,假设我们现在有抽象组件:手机,具体被装饰的类有L_phone和H_phone
public interface Phone{
public String com();
public double getprice();
}
public class L_phone implements Phone{
private String comp="低配置手机";
public String com(){
return comp;
}
public double getprice(){
return 999;
}
}
public class H_phone implements Phone{
private String comp="高配置手机";
public String com(){
return comp;
}
public double getprice(){
return 2999;
}
}
装饰器组件,起到一个中间层作用,方便后面的扩展,具体的装饰实现延迟到子类中
public class phoneDecorator implements Phone{
private String comp = "装饰器";
public String com() {
return comp;
}
public double getprice() {
return 0;
}
}
具体装饰
添加无线充电的装饰器类
public class NoWireCharge extends phoneDecorator implements Phone{
private String comp="增加无线充电功能";
private double increase= 500;
private Phone phone;
public NoWireCharge (Phone phone){
this.phone=phone;
}
public String com(){
return phone.com()+","+comp;
}
public double getprice(){
return phone.getprice()+increase;
}
}
然后让我们再来一个添加游戏勿扰的装饰器类
public class NotBotherMeWhenGame extends phoneDecorator implements Phone {
private String comp="增加游戏勿扰功能";
private double increase= 200;
private Phone phone;
public NoWireCharge (Phone phone){
this.phone=phone;
}
public String com(){
return phone.com()+","+comp;
}
public double getprice(){
return phone.getprice()+increase;
}
}
这样我们就可以实现方法的自由组合了
//只要一个普通的低配手机
Phone p=new L_phone();
//为低配手机添加无线充电功能
Phone p1=new NoWireCharge(p);
//再构造一个无线充电且游戏勿扰的低配机
Phone p2=new NotBotherMeWhenGame(NoWireCharge(p));
5.行为模式(Behavioral patterns)
5.1 策略模式(Strategy)
有不同的方法来实现同一任务,此时我们可以让用户自由选择这些方法。强调的是ADT内部某些实现功能的灵活算法转换。主要通过委托来完成
倘若我们有一个类,名字是ShoppingCart(购物车),类Items是商品,商品的构造器要求是输入商品名及价格。然后支付方法有CreditCardStrategy和PayPalStrategy,我们还是来看具体代码
//购物车类
public class ShoppingCrat{
……
public void pay(PaymentStrategy P_method){
int amount=计算商品价值总和
P_method.pay(amount);
}
}
//我们的Strategy接口
public interface PaymentStrategy{
……
public void pay(int amount);
}
//实现接口的两个类
public class CreditCardStrategy implements{
……
public void pay(int amount){
具体实现1
}
}
public class PayPalStrategy implements{
……
public void pay(int amount){
具体实现2
}
当我们实用的时候就可以这样来用啦
ShoppingCart cart= new ShoppingCart();
Item item1=new Item("商品名",价钱);
cart.additem(item1);
//下面这里就可以根据实际情况做出不同算法间的切换啦
//PayPalStrategy 或者是 CreditCardStrategy
cart.pay(new PayPalStrategy());
5.2模板模式(Template Method)
这个的概述还是容易令人理解的,就是算法框架定义抽象类,子类提供具体行为
也就是说,当多个客户端共享一套相同算法,但在一些细节上有所不同的时候,我们把共性步骤在抽象类内公共实现,差异化步骤在各个子类中实现。通过继承来完成。
小Tips:被final修饰的类没有办法被继承,被final修饰的方法无法被重写哦
仍然让我们来看代码
//抽象接口,有公用方法,和需要被实现的方法
public abstract class OderProcessTemplate{
public boolean isGift;
public abstract void doSelect();
public abstract void doPayment();
public abstract void doDelivery();
public final void GiftWrap(){
具体实现
}
public final void processOrder(){
doSelect();
doPayment();
if(isGift) GiftWrap();
doDelivery();
}
}
//然后用具体的类去实现其中的细节
//类1
public class NetOrder extends OderProcessTemplate{
@Override
public abstract void doSelect(){
具体实现1.1
}
@Override
public abstract void doPayment(){
具体实现1.2
}
@Override
public abstract void doDelivery(){
具体实现1.3
}
}
//类2
public class StoreOrder extends OderProcessTemplate{
@Override
public abstract void doSelect(){
具体实现2.1
}
@Override
public abstract void doPayment(){
具体实现2.2
}
@Override
public abstract void doDelivery(){
具体实现2.3
}
}
这样我们就实现了模板方法模式啦
调用时我们这样调用就可以了
OderProcessTemplate order=new StoreOrder();
order.processOrder();
5.3迭代器模式(Iterator)
相信大家之前都已经接触过迭代器了,那这里的迭代器模式是什么呢
有时,我么也会自己来写除了List、Set、Map以外的集合类,我们然自己的集合类实现Iterable接口,并且实现自己独特的Iterator,允许客户端利用这个迭代器进行显示或者隐式的遍历。
也就是说,迭代器有两个接口作为前提:
Iterable接口,实现该接口的集合对象是可迭代遍历的
Iterator接口,具体的迭代器实现
怎么来用?让我们看下面的代码
//iterable接口,有iterator方法
public interface Iterable<E>{
……
public Iterator<E> iterator();
}
//iterator接口,具体的迭代功能
public interface Iterator<E>{
public boolean hasNext();
public E next();
public void remove();
}
然后我们就能用这两个接口完成我们的迭代器模式啦
//我们的集合类
public class Pair<E> implements Iterable<E>{
private final E first,second;
public Pair<E>(E f,E s){
first=f;
second=s;
}
//返回用于实现迭代功能的具体迭代器
public Iterator<E> iterator(){
return new PI();
}
}
//实现迭代器
public class PI implements Iterator<E>{
……
public boolean hasNext(){
具体实现1
}
public E next(){
具体实现2
}
public void remove{
具体实现3
}
}
5.4访客模式(Visitor)
对特定类型的特定操作,在运行时把二者绑定到一起,所以可以灵活的修改操作。再具体点说,就是将数据和作用于数据上的某些特定操作分离开,主要依靠委托完成
也就是说我们可以只更改我们的visitor来修改或者扩展功能
前提代码
public interface Item{
……
//委托visitor来实现accept功能
public int accept(ShoppingCartVisitor visitor);
}
public class Book implements Item{
private double price;
……
public int accept(ShoppingCartVisitor visitor){
return visitor.visit(this);
}
}
public class Fruit implements Item{
private double weight;
……
public int accept(ShoppingCartVisitor visitor){
return visitor.visit(this);
}
}
现在让我们来实现我们的visitor
//我们的visitor接口
public interface ShoppingCartVisitor{
public int visit(Book book);
public int visit(Fruit fruit);
}
//具体实现
public class SCV_Impl implements ShoppingCartVisitor{
public int visit(Book book){
具体实现1
}
public int visit(Fruit fruit){
具体实现2
}
}
那我们调用的时候就可以这样办了
Item[] items=new Item{new Book(……1),new Book(……2),new Fruit(……3),new Fruit(……4)}
ShoppingCartVisitor visitor=new SCV_Impl();
int sum=0;
for(Item item:items)
{
sum+=item.accept(visitor);
}
这样我们的visitor就解释清楚了
一些解释
也许你看到这会心生不解,这和strategy模式好像啊,都是用委托,实现某种方法
但是他们的区别也挺大的吧
Strategy模式主要是同一种方法的不同实现形式,让用户可以自由切换方法,你像我们举的例子,对于付款我们可以让用户在银行卡支付和paypal支付工具支付两种方法中自由选择。
Visitor模式主要是面对ADT,方便我们增加其他ADT操作,或者修改ADT操作而不去改动原先的ADT,也算是遵循了开闭原则,我们举的例子也证明了这一点,我们把book和fruit的价钱计算方法拿了出去,便于我们修改,如果以后fruit的计价方式不一样了,我们可以直接修改visitor而不去改变原本的Fruit类,并且如果我们增加商品向Sugar类,我们也可以在Visitor接口和具体实现里增添Sugar的visitor。
简单的下个定义,Strategy是面向一个ADT内部某些方法灵活转换;Visitor则是面对不同ADT,增加或修改ADT操作
好啦,就到这里,
你知道毛毛虫是怎么过河的吗
——变成蝴蝶
(:D)