《Java 编程思想》学习笔记 08 | 接口

1.抽象类是普通类和接口之间的一种中庸之道

如果你不想创建某一个类的对象,而是仅仅想把它用作给子类继承,那么就可以使用抽象类,因为你无法new一个抽象类的对象,如果你new了,编译器就会报错。

2.含有抽象方法的类叫抽象类,也可以不含抽象方法,用abstract修饰类就行

抽象方法:用abstract修饰,没有方法体,用分号结尾。如:abstract void f();

抽象类:用abstract修饰,包含抽象方法,如:abstract class Father {...}

  1. 抽象类之所以不像接口那么抽象,是因为在抽象类中既可以包含抽象方法,也可以包含有定义的方法。
  2. 抽象类也可以不包含任何抽象方法,你只需要用abstract修饰即可,如果你确实不想要创建该类的对象的话。

3.继承抽象类应该重写它的所有抽象方法,也可以不这么做

  1. 如果你继承了一个抽象类,那么应该重写抽象类中的所有抽象方法。
  2. 如果你只重写了一部分,那么继承了抽象类的普通类也将成为抽象类。

4. 子类重写后的方法的访问权限不能比父类方法的访问权限低

这是Java语言强制规定并设计好的,为什么这么设定依赖于里氏代换原则:在一个软件系统中,子类应该可以替换任何基类能够出现的地方,并且经过替换以后,代码还能正常工作。举一个例子:

假设存在一个父类Father和一个子类Child,它们在一个项目的不同包中,一个父类Father拥有方法public void show(){}可以被其他任意包的任意对象调用,这个方法被子类Child这样子覆写:void show(){}实际上并不能这样子覆写,编译器会报错,我们假设可以这样子覆写),该方法将变会为默认包访问权限,它只能被本包的类所访问。

此时,假设第三个包中的一个对象调用一个方法为:

get(Father f){
    f.show();
}

而此时传入的对象为Child类对象,假设为child,此时child将转型为Father,但是child中的show()调用权限已经被缩小了,而根据方法的多态机制,被向上转型的child势必会去调用Child类的show()方法,但由于此时Child类中的show()方法是默认包访问权限,并且Father类也不在Child类的包中,根本无法访问到Child类的show()方法,这就将造成灾难性的错误!

所以Java语言就强制规定了:子类重写后的方法的访问权限不能比父类方法的访问权限低。

以上只是一个例子,还有其他出于易维护和易代码结构设计的设计思想原因。

5.接口

  1. interface代替class来定义接口。
  2. class一样,接口可以用public修饰,但仅限于该接口在与其同名的文件中被定义;如果不添加public的话就和class一样,该接口就只具有默认包访问权限。
  3. 接口中也可以定义属性,但这些属性默认是static和final的。(属性=字段=域)
  4. 普通类使用implements来实现接口。与extends不同,implements可以实现多个接口。
  5. 接口中所有未定义的方法都默认是public,这和class有很大不同(class中的方法默认是包访问权限);因此,一旦你用普通类实现了接口,那就必须在该普通类中显示地将重写的方法声明为public的,否则编译器将会报错。(原因是Java强制规定子类重写后的方法的访问权限不能低于父类方法的访问权限
//接口
interface Instrument {
    int VALUE = 5; //接口中的属性默认是static和final的
    public void play(); //接口中的方法默认是public
    void adjust(); //就算不显式地声明为public的,它也是public的
}
//类实现接口
class Wind implements Instrument {
    public void play() { //实现类重写的方法必须声明为public
        System.out.println("WindPlay");
    }
    public void adjust() { //实现类重写的方法必须声明为public
        System.out.printlb("WindAdjust");
    }
}
//类继承实现接口的类
class Brass extends Wind {
    public void play() { //子类重写的方法只能声明为public
        System.out.println("BrassdPlay");
    }
    public void adjust() { //子类重写的方法只能声明为public
        System.out.printlb("BrassAdjust");
    }
}
public class Music {
    void turn(Instrument i) {
        i.play();
        i.adjust();
    }
    public static void main(String[] args) {
        Instrument i1 = new Wind();
        Instrument i2 = new Brass();
        turn(i1);
        turn(i2);
    }
}
/* 
* Output:
* WindPlay
* WindAdjust
* BrassPlay
* BrassAdjust
*/

6.策略设计模式

将父类和子类里我们要使用的同名方法在public类里重载出来:

  1. 将顶层的父类引用看作策略作为第一个参数,提炼出要被处理的不变的东西作为第二个参数。
  2. 然后在该方法中用这俩参数去调用父类和子类中要使用的同名方法。这样,便可以在该方法中,根据所传递的参数对象的不同而具有不同的行为。
class Processor {
    Object process(Object input) {
        return input;
    }
}   
class Upcase extends Processor {
    String process(Object input) { //协变返回类型
        return ((String)input).toUpperCase(); //将传进来的input变为大写
    }
}
class Splitter extends Processor {
    String process(Object input) {
        return Arrays.toString.(((String)input).split(" ")); 
    }
}
public class Apply {
    public static String s = "I am WRJ"; //创建不变的、要被策略参数对象应用的东西
    
    public static void process(Processor p, Object s) { //策略设计模式
        System.out.println(p.process(s));
    }
    
    public static void main(String[] args) {
        process(new Upcase, s); //new Upcase就是一个策略,它应用于不变的字符串s
        process(new Splitter, s); //new Splitter也是一个策略,它也应用于不变的字符串s
    }
} 
/*
* Output:
* I AM WRJ
* [I, am, WRJ]
*/

7.完全解耦

  1. 上面的代码过于耦合,因为Processor不是一个接口,它只是一个普通的类,当你想要用其它的非String的东西应用于Processor这个结构中时,你只能继续去继承Processor,而上面的Processor这个普通类表面上看就只是用来处理String的东西,而其它程序员也只能知道这好像只能用来处理String的东西;但有一天,你发现你想用Processor去处理其它非String的东西,但这时你发现上面的代码过于耦合:上面的代码从表面上看只能用于处理String的东西,所以你要将Processor这个普通的类变为接口,提高Processor代码的可复用性,使之表面上就可以一目了然的让其可以既应用于String的东西也可以应用于非String的东西.
  2. 并且,真正要复用的代码其实更多的是Apply类的process()方法,用到了策略设计模式,但是该方法默认传入的只能是已经确定的字符串“I am WRJ”,不能用做其它的地方,所以也要将其抽离出来,已达到完全解耦的效果:
//将Processor类变为接口
package interface;

public interface Processor {
    public Object process(Object input);
}
package interface;
//将Apply抽离出来,运用策略设计模式
public class Apply {
    public static void process(Processor p, Object input) { //使之传入的Object可以根据需要而更改,同时运用到了策略设计模式
        System.out.println(p.process(input));
    } 
}

//实现Processor接口,使之可以处理String的东西
package interface;

public abstract class StringProcessor implements Processor { //这里用到了抽象类去实现Processor接口
    //Processor接口里的抽象方法重新重写为抽象方法,并且应用了协变返回类型,将Object变为String,使之从表面上就能知道是处理String的东西,提高了Processor接口里的代码的复用性
    public abstract String process(Object s); 
    
    public static String s = "I am WRJ"; //这就是我们还要传入的Object,它是String类型
    
    public static void main(String[] args) { //程序执行
        Apply.process(new Upcase(), s); //运用策略设计模式
        Apply.process(new Splitter(), s); //运用策略设计模式
    }
}

class Upcase extends StringProcessor {
    public String process(Object input) {
        return return ((String)input).toUpperCase(); //将传进来的input变为大写
    }
}
class Split extends StringProcessor {
    public String process(Object input) {
        return Arrays.toString.(((String)input).split(" ")); 
    }
}

上面的代码就演示了接口+策略设计模式是如何提高代码的复用性使之达到完全解耦的效果的,真的有点难理解呢!

8.适配器设计模式

在实际开发中,你可能还要将一些具有相同结构的类,运用到上面的Apply方法中,而这个类并不是Processor,所以你要用到适配器设计模式,适配器通过实现Processor接口并将你所拥有的类变成适配器的属性。

例如,现在有一个电子过滤器类,它和Processor接口有相同的结构:

package interface;

public class Filter {
    public Waveform process(Waveform input) {} //与Processor接口有相同的方法processor
}
package interface;

class FilterAdapter implements Processor { //适配器设计模式
    Filter filter;
    public FilterAdapter(Filter filter) {
        this.filter = filter;                                 
    }
} 
Processor processor = new FilterAdapter(new Filter()); //将Filter通过适配器FilterAdapter转型为Processor

9.继承与多重实现

interface CanFight {
    void fight();
}
interface CanSwim {
    void swim();
}
interface CanFly {
    void fly();
}
class ActionCharacter {
    public void fight() {}
}

class Hero extends ActionCharacter implements CanFight, CanSwim, CanFly {
    //public void fight() {}  //不必实现CanFight接口的fight方法,因为Hero继承的ActionCharater里已经有了fight方法,并且特征签名与CanFight类的fight方法一样,所以Hero已经隐式的拥有了fight方法,不必再实现
    public void swim() {}
    public void fly() {}
}

public class Adventure {
    public static void t(CanFight x) {x.fight();}
    public static void t(CanSwim x) {x.swim();}
    public static void t(CanFly x) {x.fly();}
    public static void t(ActionCharater x) {x.fight();}
    public static void main(String[] args) {
        Hero h = new Hero();
        t(h);
        u(h);
        v(h);
        w(h);
    }
}
/* 
* Hero对象可以向上转型为任何一个接口和父类。
*/
  1. 当既要继承类又要实现接口时,必须将继承类放在实现接口前面,否则编译器将会报错。
  2. 使用接口的核心原因:为了能够向上转型为多个基类型以及由此带来的灵活性。

10.接口可以继承接口,并且不止可以继承一个

  1. 接口可以通过继承接口来使其进行拓展,从而产生一个包含所继承接口的新接口。
  2. 接口继承接口不像类继承类一样只能继承一个,接口可以继承多个接口
  3. 类继承一个类类实现多个接口接口继承多个接口
  4. 类不能继承接口,接口不能继承类。
  5. 接口这个东西,只要涉及到了,它都可以多次被组合:不管是类实现多个接口也好还是接口继承多个接口也好。因为没有任何与接口相关的存储:因此,也就无法阻止多个接口的组合

11.请不要试图在要进行组合的多个接口中使用相同的方法名

这会造成代码的严重混乱,请尽量避免这种情况的发生。

12.再论策略设计模式

使用策略设计模式让方法接受接口类型,是一种可以让任何类都可以对该方法进行适配的方式,这就是使用接口而不是类的强大之处。

public interface Processor {
    public Object process(Object input);
}
public class Apply {
    public static void process(Processor p, Object input) { //策略设计模式
        p.process(input);
    } 
}

上面的​Apply​类中有一个​process​方法,它接收一个以​Processor​接口为类型的参数,此时,当你想要将一个新的类型,如​Filter​类,应用于​Apply​类的​process​方法,只需将​Filter​类去实现​Processor​接口即可:

class Filter implements Processor {
    public Object process(Object input) {
        ...
    }
}

这种策略设计模式使其它类只要遵循了某个接口,便可以使用以该接口为参数类型的任何方法,这样做会使得这些方法更加灵活、通用、并更具可复用性。

13.接口中的域:默认都是public static final

  1. 在接口定义的域不能是空final,也就是必须给予手动的初始化。
  2. 接口中的域可以使用常量直接初始化也可以使用非常量表达式进行初始化。
  3. 接口中的域并不是接口的一部分,它们存在于接口的静态存储区域中。

14.接口可以嵌套在类或其它接口中

这点目前我觉得是有点偏僻的,暂时先知道有这回事就好。

15.策略设计模式与工厂方法设计模式

策略设计模式:

interface Game {
    void play();
}
class checkers implements Game {
    public void play() {
        System.out.println("Checkers is very funny!");
    }
}
class chess implements Game {
    public void play() {
        System.out.println("Chess is very funny!");
    }
}
public class Games {
    public static void playGame(Game game) {
        game.play()
    }
    public tatic void main(String[] args) {
        Checkers checkers = new Checkers();
        playGame(checkers);
        Chess chess = new chess();
        palyGame(chess);
    }
}

工厂方法设计模式:

interface Game {
    void play();
}
class checkers implements Game {
    public void play() {
        System.out.println("Checkers is very funny!");
    }
}
class chess implements Game {
    public void play() {
        System.out.println("Chess is very funny!");
    }
}
//****************************************************//
interface GameFactory {
    Game getGame();
}
class checkersFactory implements GameFactory {
    Game getGame() {
        return new chcekers();
    }
}
class chessFactory implements GameFactory {
    Game getGame() {
        return new chess();
    }
}
//****************************************************//
public class Games {
    public static void playGame(GameFactory factory) {
        Game game = facory.getGame();
        game.play();
    }
    public tatic void main(String[] args) {
        playGame(new checkersFactory());
        palyGame(new chessFactory());
    }
}

上面的工厂方法设计模式中,​checkersFactory​工厂调用​getGame()​方法,生成了遵循​Game​接口的​checkers​对象。这与直接调用构造器生成​checkers​对象不同,我们在工厂对象上调用的是创建方法,而该工厂对象将生成接口的某个实现的对象。理论上,通过这种方式,我们的代码将完全与接口的实现分离,这就使得我们可以透明地将某个实现替换为另一个实现。如果不是工厂方法,你的代码就必须在某处指定将要创建的Service的确切类型,以便调用合适的构造器。

总结:没有总结!目前我自己通过摸索,知道策略模式与工厂方法模式非常非常相似,并且发现了策略设计模式与工厂方法设计模式是“或”的关系,这两种设计模式没有谁好谁坏,都是非常优秀的设计模式但我实在发现不了这两者应该用于何处时更有利,只能等到以后是否有机会去接触到了

16.用类还是接口

恰当的原则应该是优先选择类而不是接口。从类开始,如果接口的必须性变得非常明确,那么就进行重构。接口是一种重要的工具,但是它们容易被滥用。

注:参考《Java编程思想》p189

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值