这篇笔记主要是抽象类和接口,还有简单介绍下三种设计模式:模板模式、工厂模式、命令模式
1.抽象方法和抽象类
(1)抽象方法和抽象类都必须使用abstract修饰符来定义,包含抽象方法的类只能被定义成抽象类,抽象类里面可以没有抽象方法。抽象方法里面没有方法体。
(2)抽象类不能被实例化,不能使用new关键字来调用抽象类的构造器来创建抽象类的实例,只能被当成父类来继承。
package cn.lsl; public abstract class Shape { { System.out.println("抽象类的中的普通初始化块"); } private String color; public abstract double calPerimeter(); public abstract String getType(); public Shape(){} public Shape(String color){ System.out.println("Shape的构造器"); this.color = color; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } }
package cn.lsl; public class Triangle extends Shape { private double a; private double b; private double c; public Triangle(String color, double a, double b, double c) { super(color); this.setSides(a, b, c); } public void setSides(double a, double b, double c){ if(a>=b+c || b>=a+c || c>=a+b){ System.out.println("三角形两边之和必须大于第三边"); return; } this.a = a; this.b = b; this.c = c; } @Override public double calPerimeter() { return a + b + c; } @Override public String getType() { // TODO Auto-generated method stub return "三角形"; } }
package cn.lsl; public class Circle extends Shape{ private double radius; public Circle(String color, double radius){ super(color); this.radius = radius; } public void setRadius(double radius) { this.radius = radius; } @Override public double calPerimeter() { return 2 * Math.PI * radius; } @Override public String getType() { return getColor() + "圆形"; } public static void main(String[] args) { Shape s1 = new Triangle("黑色",3,4,5); Shape s2 = new Circle("黄色",3); System.out.println(s1.getType()); System.out.println(s1.calPerimeter()); System.out.println(s2.getType()); System.out.println(s2.calPerimeter()); } }
2.接口
(1)接口是一种特殊的“抽象类”,接口里面不能包含普通方法,接口里面的方法都是抽象方法。
(2)接口定义的是一种规范,实现这个接口的类所要遵守的规范,接口不关心这些类内部的状态数据,也不关心这些类里方法的实现细节,它只是规定这批类里面必须提供哪些方法。
(3)定义接口使用的是interface关键字,接口可以有多个直接父接口,但接口只能继继承接口,不能继承类。
[修饰符] interface 接口名 extends 父接口1, 父接口2{ }
(4)接口里面不能包含构造器和初始化块,接口里面可以包含Field(只能是常量)、方法(只能是抽象方法)、内部类、枚举类
因为接口里面只能定义常量Field,所以以下两行代码的结果是一样的
int a = 15; public static final int a = 15;
(5)因为接口里面定义的全部是抽象方法,所以接口里面不允许定义静态方法(不能使用static修饰接口里面定义的方法)
(6)当接口扩展父接口的时候,将会获得接口里面定义的所有抽象方法、常量、内部类、枚举类。
package cn.lsl; interface interfaceA{ int a = 1; void testA(); } interface interfaceB{ int b = 2; void testB(); } interface interfaceC extends interfaceA, interfaceB{ int c = 3; void testC(); } public class InterfaceTest { public static void main(String[] args){ System.out.println(interfaceC.a); System.out.println(interfaceC.b); System.out.println(interfaceC.c); } }
因为interfaceC接口继承了interfaceA和interfaceB,所以interfaceC接口中获得他们的Field常量
可以通过接口名来访问常量Field
(7)一个类可以继承一个父类,并同时实现多个接口,实现接口用implements关键字。
一个类实现了一个或多个接口后,这个类必须完全实现这些接口里所定义的全部抽象方法,若这个类保留了从父类中继承到的抽象方法,那么这个类也必须定义成抽象类。
格式:
[修饰符] class 类名 extends 父类 implements 接口1, 接口2{ }
eg:一个模拟打印机的例子
package cn.lsl; public interface Output { int MAX_CACHE_LINE = 50; void out(); void getData(String msg); }
package cn.lsl; public interface Product { int getProduceTime(); }
package cn.lsl; public class Printer implements Output, Product { private String[] printData = new String[MAX_CACHE_LINE]; private int dataNum = 0; @Override public int getProduceTime() { // TODO Auto-generated method stub return 45; } @Override public void out() { // TODO Auto-generated method stub while(dataNum > 0){ System.out.println("打印机打印:" + printData[0]); //把作业队列整体前移一位,并将剩下的作业数减1 System.arraycopy(printData, 1, printData, 0, --dataNum); } } @Override public void getData(String msg) { // TODO Auto-generated method stub if(dataNum >= MAX_CACHE_LINE){ System.out.println("输出队列已满,添加失败"); }else{ printData[dataNum++] = msg; } } public static void main(String[] args) { Output o = new Printer(); o.getData("Java"); o.getData("JavaSe"); o.out(); o.getData("Android"); o.getData("JavaEE"); o.out(); Product p = new Printer(); System.out.println(p.getProduceTime()); Object obj = p; } }
3.模板模式
如果一个抽象父类中提供了多个子类的通用方法,并把一个或多个方法留给其子类实现,这就是一种模板设计模式。
例如以下一个例子,父类中的普通方法依赖于一个抽象方法,抽象方法则推迟到子类中提供实现。
package cn.lsl; public abstract class SpeedMeter { private double turnRate; public SpeedMeter(){} public abstract double getRadius(); public void setTurnRate(double turnRate){ this.turnRate = turnRate; } public double getSpeed(){ return Math.PI * 2 * getRadius() * turnRate; } }
package cn.lsl; public class CarSpeedMeter extends SpeedMeter{ @Override public double getRadius() { return 0.28; } public static void main(String[] args) { CarSpeedMeter csm = new CarSpeedMeter(); csm.setTurnRate(15); System.out.println(csm.getSpeed()); } }
4.工厂模式
接口体现的是一种规范和实现分离的思想,利用接口可以极好地降低程序各模块之间的偶尔,从而提高系统的可扩展性和可维护性。
一个情景案例:
假设有一个Conputer类要组合一个输出设备,有两种选择,一是让Computer类组合一个Printer,二是让Computer类组合一个Output。
假设让Computer类组合一个Printer对象,如果有一天要使用BetterPrinter来代替Printer,于是就要对Computer类源代码进行修改。如果系统中多个类组合了Printer,那么修改的工作量将非常大。
而如果采用Computer类组合一个Output类型的对象,将Computer类与Printer类完全分离。那么当Printer对象切换到BetterPrinter对象时,系统完全不受影响。
package cn.lsl; public class Computer { private Output out; public Computer(Output out){ this.out = out; } public void keyIn(String msg){ out.getData(msg); } public void print(){ out.out(); } }
上面的程序Computer不再负责创建Output对象,而是提供一个Output工厂来负责生成Output对象。
package cn.lsl; public class OutputFactory { public Output getOutput(){ //return new Printer(); return new BetterPrinter(); } public static void main(String[] args) { OutputFactory of = new OutputFactory(); Computer c = new Computer(of.getOutput()); c.keyIn("Java"); c.keyIn("JavaSe"); c.print(); } }
上面程序由getOutput()这个方法返回一个Output实现类的实例,具体要创建哪个对象由该方法决定。如果要将Printer改成BetterPrinter实现类,只要让BetterPrinter实现Output接口,
并改变OutputFactory类中的getOutput方法即可。
package cn.lsl; public class BetterPrinter implements Output, Product { private String[] printData = new String[MAX_CACHE_LINE]; private int dataNum = 0; @Override public int getProduceTime() { // TODO Auto-generated method stub return 45; } @Override public void out() { // TODO Auto-generated method stub while(dataNum > 0){ System.out.println("高速打印机打印:" + printData[0]); //把作业队列整体前移一位,并将剩下的作业数减1 System.arraycopy(printData, 1, printData, 0, --dataNum); } } @Override public void getData(String msg) { // TODO Auto-generated method stub if(dataNum >= MAX_CACHE_LINE){ System.out.println("输出队列已满,添加失败"); }else{ printData[dataNum++] = msg; } } public static void main(String[] args) { Output o = new BetterPrinter(); o.getData("Java"); o.getData("JavaSe"); o.out(); o.getData("Android"); o.getData("JavaEE"); o.out(); Product p = new BetterPrinter(); System.out.println(p.getProduceTime()); Object obj = p; } }
并改变
public Output getOutput(){ return new BetterPrinter(); }
5.命令模式
有这样一个场景:某个方法要完成某一个行为的时候,但这个行为的具体实现无法确定,必须等到执行该方法时候才可以确定。这个方法不仅需要普通数据可以变化,甚至还有方法执行体也需要变化。
我们可以用命令模式来完成这个场景。
package cn.lsl; public interface Command { void process(int[] target); }
Command接口里面定义了一个process方法,这个方法没有方法体,因为无法确定这个处理行为
package cn.lsl; public class ProcessArray { public void process(int[] target, Command cmd){ cmd.process(target); } }
ProcessArray类里面包含一个process方法,这个方法无法确定处理数组的行为,所以传入一个参数,这个Command参数负责对数组的处理行为。
通过Command接口,实现ProcessArray类与具体“处理行为”分离。
package cn.lsl; public class PrintCommand implements Command{ @Override public void process(int[] target) { // TODO Auto-generated method stub for(int tmp : target){ System.out.println("迭代数组:" + tmp); } } }
package cn.lsl; public class AddCommand implements Command{ @Override public void process(int[] target) { // TODO Auto-generated method stub int sum = 0; for(int tmp : target){ sum += tmp; } System.out.println("数组总和:" + sum); } }
package cn.lsl; public class CommandTest { public static void main(String[] args) { ProcessArray pa = new ProcessArray(); int[] target = {1,2,5,3}; pa.process(target, new PrintCommand()); System.out.println("---------------"); pa.process(target, new AddCommand()); } }