设计原则
单一职责:每个类或每个方法每个框架只做一件事
反例:统计文本文件中有多少个单词
public static void main(String[] args) {
try {
//=========================负责加载路径下的文件
Reader in = new FileReader("F:\\1.txt");
BufferedReader br = new BufferedReader(in);
String line = null;
StringBuilder sb = new StringBuilder("");
while ((line =br.readLine())!=null){
sb.append(line);
sb.append("");
}
//==========================负责根据正则分割字符串
String [] words = sb.toString().split("[^a-zA-Z]+");
System.out.println(words.length);
br.close();
} catch (IOException e) {
e.printStackTrace();
}
上面的写法就违反了单一职责,同一个main方法中,我们即让它去加载文件,也让它去做分割,这样做的坏处是,每当我们需要调用其中的一个功能时(如我有文件只需要分割),仍需要重写一遍。
正例:
//==============只负责根据路径加载文件
public String loadFile(String path) throws IOException {
Reader in = new FileReader(path);
BufferedReader br = new BufferedReader(in);
String line = null;
StringBuilder sb = new StringBuilder("");
while ((line = br.readLine()) != null) {
sb.append(line);
sb.append("");
}
br.close();
return sb.toString();
}
//=========只负责字符串根据正则分割
public int Textlength(String sb, String regex){
String [] words = sb.split(regex);
return words.length;
}
public static void main(String[] args) throws IOException {
String str = loadFile("F:\\1.txt");
String regex ="[^a-zA-Z]+";
System.out.println(Textlength(str,regex));
}
通过单一职责,可以提高代码的重用性,通过单一职责的方法得到的数据我们不再有耦合,拿来可以做的事也不再局限。
开闭原则:对扩展开放(新功能),对修改关闭(旧功能)
创建了一个汽车对象
class Car {
private String band;
private String color;
private float price;
public String getBand() {
return band;
}
public void setBand(String band) {
this.band = band;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
@Override
public String toString() {
return "Car{" +
"band='" + band + '\'' +
", color='" + color + '\'' +
", price=" + price +
'}';
}
}
public static void main(String[] args) {
Car car = new Car();
car.setBand("bm");
car.setColor("red");
car.setPrice(13.3f);
System.out.println(car.toString());
}
当变化来临时,例如汽车的价格现在需要打折(打8折),这时,我们在Car的源代码中修改,就违反了开闭原则
反例:修改了源代码
public void setPrice(float price) {
this.price = (price*0.08f);
}
开发时,我们应该要去考虑变化的需求,属性会在任何时刻都有可能产生变化
对待变化时,我们应该选择扩展,而不是去修改源代码(ps:在强制要求符合开闭原则的情况下!)
正例:
//进行扩展
class DiscountCar extends Car{
@Override
public void setPrice(float price) {
super.setPrice(price*0.08f);
}
}
public static void main(String[] args) {
//===使用向上转型时,方法的调用只和new的对象有关
Car car = new DiscountCar();
car.setBand("bm");
car.setColor("red");
car.setPrice(130000f);
System.out.println(car.toString());
}
开闭原则应该遵循应用场景去考虑,如果源代码就是你自己写的,而且需求是稳定的,那么,直接修改源代码也是一个简单的做法,但当源代码是别人的代码或架构是,我们就要去符合开闭原则,防止破坏结构的完整性!
依赖倒置原则:上层不能依赖于下层,它们都应该依赖于抽象
UML类图规则:
关联:一个类的对象,作为另一个类的字段
画法:实线加箭头
class Test{
}
class Test2{
private Test test;
//Test2关联Test
//Test被关联
}
依赖:一个类的方法中定义了另一个类作为局部字段
画法:虚线加箭头
class Test{
}
class Test2{
pubilic void test(){
Test test= new Test;
}
}
继承:画法 实现加空心箭头
实现:画法 虚线加空心箭头
依赖倒置原则:
反例:人喂养动物
static class Person{
public void feed(Dog dog){
dog.eat();
}
}
static class Dog{
public void eat() {
System.out.println("狗啃骨头");
}
}
public static void main(String[] args) {
Person p = new Person();
Dog d = new Dog();
p.feed(d);
}
在上面的代码中,人要喂狗,依赖于有一条狗,人作为上层依赖于下层,这样有什么坏处呢?
坏处是,当变化来临时,比如,人又养了一只猫,那么上层人这个类当中,就必须在添加喂猫的方法,每当下层变动时,上层也会跟着变动,而我们希望下层变动时,上层不会跟着改变
正例:他们都应该依赖于抽象
interface Animal{
void eat();
}
static class Person{
public void feed(Animal animal){
animal.eat();
}
}
static class Dog implements Animal{
public void eat() {
System.out.println("狗啃骨头");
}
}
public static void main(String[] args) {
Person p = new Person();
Dog d = new Dog();
p.feed(d);
}
依赖倒转原则就是指:代码要依赖于抽象的类,而不要依赖于具体的类;要针对接口或抽象类编程,而不是针对具体类编程。通过面向接口编程,抽象不应该依赖于细节,细节应该依赖于抽象。
接口隔离原则:设计接口时,接口的抽象应该是有意义的
反例:动物接口中定义的方法并不是被所有动物需要的
interface Animal{
void eat();
void fly();
void swim();
}
class Bird implements Animal{
@Override
public void eat() {
System.out.println("吃");
}
@Override
public void fly() {
System.out.println("飞");
}
//======鸟不会游泳,并不需要实现
@Override
public void swim() {
System.out.println("游泳");
}
}
正例:接口抽象出有意义的层级,供需要的类去实现
interface Flyable{
void fly();
}
interface Swimable{
void swim();
}
interface Eatable{
void eat();
}
class Bird implements Flyable,Eatable{
.....
}
class Dog implements Swimable,Eatable{
.....
}
客户端不应该依赖那些它不需要的接口。
一旦一个接口太大,则需要将它分割成一些更细小的接口,使用该接口的客户端仅需知道与之相关的方法即可
迪米特法则(最少知道原则):封装,只和朋友通信
什么是朋友?
1.类中的字段
2.方法的返回值
3.方法的参数
4.方法中的实例对象
5.对象本身
6.集合中的泛型
最少知道原则
反例:关闭电脑的流程
class Computers{
public void closeFile(){
System.out.println("关闭文件");
}
public void closeScreen(){
System.out.println("关闭屏幕");
}
public void powerOff(){
System.out.println("断电");
}
}
class Person{
private Computers computers;
public void offComputers(){
computers.closeFile();
computers.closeScreen();
computers.powerOff();
}
}
当用户关闭电脑时,需要调用计算机的各个方法,但是这些方法的细节太多了,会出现用户流程出错,遗漏调用等等,对于用户来言,他只需要知道关机按钮就够了
正例:封装细节,提供接口
class Computers{
public void closeFile(){
System.out.println("关闭文件");
}
public void closeScreen(){
System.out.println("关闭屏幕");
}
public void powerOff(){
System.out.println("断电");
}
public void offComputers(){
closeFile();
closeScreen();
powerOff();
}
}
class Person{
private Computers computers;
public void offComputers(){
computers.offComputers();
}
}
里氏替换原则: 任何能用父类对象的地方,都能透明的使用子类替换
ps:子类替换父类时,不能比父类的访问修饰更严格,不能抛出父类不存在的异常
使用时,需要考虑:
1.是否有is-a的关系
2.有is-a关系后,要考虑子类替换父类后会不会出现逻辑变化
反例:
class Counter {
public int add(int i,int j) {
return i+j;
}
}
class soonCounter extends Counter{
@Override
public int add(int i, int j) {
return i-j;
}
}
public static void main(String[] args) {
Counter c = new Counter();
System.out.println(c.add(100,200));
//结果300
Counter c = new soonCounter();
System.out.println(c.add(100,200));
//结果-100
}
我们要知道,在发生向上转型的过程时,方法的调用只和new的对象有关系,所以造成了不同的结果
使用里氏替换原则时,要知道 子类可以扩展父类的功能,但不能改变父类原有的功能
组合优于继承:复用别人代码时,应该使用组合
反例:继承hashset,重写父类add方法,每次add元素时count+1;
class Counter extends HashSet {
private int count =0;
public boolean add(int i) {
count++;
return super.add(i);
}
public int getCount(){
return count;
}
}
public static void main(String[] args) {
Counter c = new Counter();
c.add(1);
c.add(2);
c.add(3);
System.out.println(c.getCount());
}
这样写会出现在hashset方法中,不只是add可以添加元素的问题,还有其他方式可以添加,如果是其他方式添加的,count就不会累加,这样是有问题的
@Override
public boolean addAll(Collection c) {
count=count+c.size();
return super.addAll(c);
}
假如我们继续重写addAll方法添加相应的判断时,又会出现新的问题,我们的count并没有正确的累计,因为在HashSet的源码addAll方法中,回调了add方法,并没有解决需求
那我们不重写addAll,反正它会回调add,完成计数,就没有问题了吗?
其实并不能解决问题,hashset的源码我们不能保证永远不会更改,假如在下一个版本中,hashset的作者更改了addAll方法,那么我们的功能也会不能正常实现了!
当继承的父类作者不是我们自己的时候,我们没有办法保证父类代码不会变更,假如我们继承了这个父类,那么我们最好是只去复用父类的代码,避免去重写或新建方法,防止源码结构变更带来的打击
也就是说,在我们需要重用代码,并且重用的代码作者并不是我们自己的时候,我们要采用组合的方式。
正例:组合优于继承
static class Counter {
HashSet hashSet = new HashSet();
private int count =0;
public boolean add(int i) {
count++;
return hashSet.add(i);
}
public boolean addAll(Collection c) {
count=count+c.size();
return hashSet.addAll(c);
}
public int getCount(){
return count;
}
}
public static void main(String[] args) {
Counter c = new Counter();
c.add(1);
c.add(2);
c.add(3);
System.out.println(c.getCount());
}
这样来写,我们类中的add和addAll方法跟HashSet中的add和addAll方法的不在有关系,也能解决这个问题
设计模式
工厂模式:提供了一种创建对象的最佳方式
简单工厂模式
正例:创建产品接口,具体产品实现,工厂类负责生产,使用时找到工厂类拿到对应产品
class Test {
interface Food {
void eat();
}
static class Bun implements Food {
@Override
public void eat() {
System.out.println("包子");
}
}
static class SteameDread implements Food {
@Override
public void eat() {
System.out.println("馒头");
}
}
static class FoodFactory {
public Food getFood(int n) {
Food food = null;
switch (n) {
case 1:
food = new Bun();
break;
case 2:
food = new SteameDread();
break;
}
return food;
}
}
public static void main(String[] args) {
FoodFactory foodFactory = new FoodFactory();
foodFactory.getFood(1).eat();
}
}
工厂模式
正例:为了进行扩展,不违反开闭原则
class Test {
interface Food {
void eat();
}
static class Bun implements Food {
@Override
public void eat() {
System.out.println("包子");
}
}
static class SteameDread implements Food {
@Override
public void eat() {
System.out.println("馒头");
}
}
static interface FoodFactory{
Food getFood();
}
static class BunFactory implements FoodFactory{
@Override
public Food getFood() {
return new Bun();
}
}
static class SteameDreadFactory implements FoodFactory{
@Override
public Food getFood() {
return new SteameDread();
}
}
static class Business {
public void test(FoodFactory foodFactory){
Food food = foodFactory.getFood();
food.eat();
}
}
public static void main(String[] args) {
Business business = new Business();
business.test(new SteameDreadFactory());
//business.test(new BunFactory());
}
}
把工厂接口化,抽象产品和接口工厂。
具体产品实现抽象产品,具体工厂实现接口工厂
一个业务类
通过调用业务类的方法,将具体工厂传入就能道道具体产品
优点:在不改变源代码的情况下,可以方便的进行扩展,比如突然增加了产品:面条。我们不需要去改写源代码,直接创建具体产品类实现抽象产品,在创建具体工厂类实现接口工厂,就可以通过调用业务的方法来得到我们新增加的产品了,符合了开闭原则。
缺点:当业务需要的类型变多,目前只有食物,当产生饮料,日用品等类别时,我们又要创建新的工厂来实现,造成代码重复的坏味道。
抽象工厂模式
变化来了,工厂模式固然很好用,但我们现在要求不光可以生产食物,还可以生产饮料,这时,按照工厂模式我们就不得不去创建抽象的饮料工厂,造成新增类别产品就要新增接口工厂的问题。
正例:为了解决这个问题,我们直接将工厂抽象出来,工厂可以生产不同的产品
class Test {
interface Food {
void eat();
}
interface Drink {
void drink();
}
static class Bun implements Food {
@Override
public void eat() {
System.out.println("包子");
}
}
static class SteameDread implements Food {
@Override
public void eat() {
System.out.println("馒头");
}
}
static class CoCo implements Drink {
@Override
public void drink() {
System.out.println("奶茶");
}
}
static class Tea implements Drink {
@Override
public void drink() {
System.out.println("茶");
}
}
static interface Factory{
Food getFood();
Drink getDrink();
}
static class BunFactoryAndCoCo implements Factory{
@Override
public Food getFood() {
return new Bun();
}
@Override
public Drink getDrink() {
return new CoCo();
}
}
static class SteameDreadAndTeaFactory implements Factory{
@Override
public Food getFood() {
return new SteameDread();
}
@Override
public Drink getDrink() {
return new Tea();
}
}
static class Business {
public void test(Factory factory){
Food food = factory.getFood();
food.eat();
Drink drink =factory.getDrink();
drink.drink();
}
}
public static void main(String[] args) {
Business business = new Business();
business.test(new SteameDreadFactory());
//business.test(new BunFactory());
}
抽象工厂中,我们把原本的接口工厂,抽象化,使其可以生产多个产品,具体的产品工厂去实现这个工厂,重写他的方法返回需要的产品,业务中,我们只需要依赖抽象工厂,返回抽象工厂的具体实现就可以,这样,调用是我们就只需要调用业务的方法,传入我们需要的具体产品工厂就可以了。
建造者模式:构建与它的表示分离
正例:
class Computer {
String Cpu;
String Gpu;
String Hd;
get//set
}
interface ComputerBuilder {
void setCpu();
void setGpu();
void setHd();
Computer getComputer();
}
class lowComputer implements ComputerBuilder {
private Computer computer = new Computer();
@Override
public void setCpu() {
computer.setCpu("i3");
}
@Override
public void setGpu() {
computer.setGpu("hd4000");
}
@Override
public void setHd() {
computer.setHd("机械500");
}
@Override
public Computer getComputer() {
return computer;
}
}
class MediumComputer implements ComputerBuilder {
private Computer computer = new Computer();
@Override
中配重写
}
class AdvancedComputer implements ComputerBuilder {
private Computer computer = new Computer();
@Override
高配重写
}
//指挥者负责调用哪一个建造者
class Commander {
private ComputerBuilder computerBuilder;
public Commander(ComputerBuilder computerBuilder) {
this.computerBuilder = computerBuilder;
}
public Computer builder(){
computerBuilder.setCpu();
computerBuilder.setGpu();
computerBuilder.setHd();
return computerBuilder.getComputer();
}
}
class AppTest{
public static void main(String[] args) {
lowComputer lowComputer = new lowComputer();
MediumComputer mediumComputer = new MediumComputer();
AdvancedComputer advancedComputer = new AdvancedComputer();
Commander commander = new Commander(advancedComputer);
System.out.println(commander.builder().toString());
}
}
适配器模式:根据已有接口,生成想要的接口
正例:
class Util{
public static void walk(Iterator iterator){
while (iterator.hasNext()){
Object o= iterator.next();
System.out.println(o);
}
}
}
class EnumerationAdapter implements Iterator{
private Enumeration enumeration;
public EnumerationAdapter(Enumeration enumeration) {
this.enumeration = enumeration;
}
@Override
public boolean hasNext() {
return enumeration.hasMoreElements();
}
@Override
public Object next() {
return enumeration.nextElement();
}
}
class Test {
public static void main(String[] args) {
List list = new ArrayList(Arrays.asList(1,2,3,4,5));
Set set = new HashSet(Arrays.asList(10,20,20,30,40));
Util.walk(list.iterator());
Util.walk(set.iterator());
Enumeration enumeration = new StringTokenizer("haha,heihei,xixi,zizi");
EnumerationAdapter e = new EnumerationAdapter(enumeration);
Util.walk(e);
}
我们拥有一个工具类Util,可以传入Iterator将其中的元素迭代展示出来,但是Enumeration这个数据结构不能放入我们的工具类中,我们通过适配器EnumerationAdapter,将Enumeration关联,在new时通过构造器中拿到它的值,通过实现Iterator,实现其中的方法,因为EnumerationAdapter实现了Iterator,就可以被我们的工具类Util使用了。
装饰器模式:允许向一个现有的对象添加新的功能,同时又不改变其结构
反例:我们有一个奶茶店,但是该店并不是所有的饮料都是一个名字和一个价钱,所有我们用了以下的写法,将变化的价格设为抽象的,因为每个饮料都有名字,我们将他设为构造器中生成
class Test {
static abstract class CoCo{
private String name;
public CoCo(String name){
this.name=name;
}
abstract int price();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
static class MilkyTea extends CoCo{
public MilkyTea() {
super("奶茶:");
}
@Override
int price() {
return 2;
}
}
static class Tea extends CoCo{
public Tea() {
super("茶:");
}
@Override
int price() {
return 1;
}
}
public static void main(String[] args) {
CoCo coCo = new MilkyTea();
System.out.println(coCo.getName()+coCo.price());
}
}
变化来了,coco奶茶店突然要为每个饮品都添加调料来进行销售,此时,我们如果为每个饮品单独添加调料属性的话,(创建新的类)会出现很多种组合的情况,且调料种类越多组合越多,导致出现类爆炸的情况
而如果我们去在父类里去添加对应的调料字段,在子类中分别判断时,则又违反了开闭原则
这时,使用装饰者模式
正例:
class Test {
static abstract class CoCo{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public CoCo(String name){
this.name=name;
}
abstract int price();
}
static abstract class Condiments extends CoCo{
protected CoCo CoCo;
public Condiments(CoCo coCo) {
super("调味料");
this.CoCo=coCo;
}
}
static class Pearl extends Condiments{
public Pearl(CoCo name) {
super(name);
}
@Override
int price() {
return CoCo.price()+1;
}
@Override
public String getName() {
return CoCo.getName()+"珍珠";
}
}
static class Mesona extends Condiments{
public Mesona(CoCo name) {
super(name);
}
@Override
int price() {
return CoCo.price()+2;
}
@Override
public String getName() {
return CoCo.getName()+"仙草";
}
}
static class MilkyTea extends CoCo{
public MilkyTea() {
super("奶茶:");
}
@Override
int price() {
return 2;
}
}
static class Tea extends CoCo{
public Tea() {
super("茶:");
}
@Override
int price() {
return 1;
}
}
public static void main(String[] args) {
CoCo coCo = new MilkyTea();
Pearl pearlMilkyTea = new Pearl(coCo);
Mesona mesona = new Mesona(pearlMilkyTea);
System.out.println(mesona.getName()+mesona.price());
}
}
将调料类抽象继承并且CoCo,构造方法中,将父类作为参数设置关联值,如果有新的调料,例如珍珠,就继承调料类,重写调料类父类的两个方法,改变价格和名字
在使用时,我们通过向上转型得到具体饮料,添加调味料时,将饮料放入构造中,这样珍珠类的构造就设置好了饮料的值,并重写方法加以修改,得到我们想要的效果
装饰者模式另一个例子
通过装饰的方式把接口变成我们想要的接口,将原来只能读字节的Reader变成了读整行的功能。
class Test extends Reader {
private Reader in;
public Test(Reader in) {
this.in = in;
}
@Override
public int read(char[] cbuf, int off, int len) throws IOException {
return 0;
}
public String readLine() throws IOException {
StringBuilder str = new StringBuilder();
while (true) {
int n = in.read();
if(n=='\n'){
break;
}
str.append((char) n);
}
return str.toString();
}
@Override
public void close() throws IOException {
in.close();
}
}
class AppTest {
public static void main(String[] args) throws IOException {
Reader reader = new FileReader("F:\\1.txt");
Test t = new Test(reader);
System.out.println(t.readLine());
}
}
模板设计模式:基于抽象的封装
正例:将不会改变的地方变为模板,而会发生改变的部分代码变成抽象的,这样调用它的人负责实现,来实现它需要的效果
abstract class Test{
public void templent(){
long start = System.currentTimeMillis();
code();
long end = System.currentTimeMillis();
System.out.println(end-start);
}
abstract void code();
}
class AppTest{
static class ArrayTest extends Test{
@Override
void code() {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
list.add(0,1);
}
}
}
public static void main(String[] args) {
Test test= new ArrayTest();
test.templent();
}
}
策略模式:一个类的行为可以在运行时更改
我们知道鸭子这个动物,每个鸭子都会吃和叫,但是鸭子们的外观都长得不一样,我们就设计了一个类
鸭子类
static abstract class Duck{
public void eat(){
System.out.println("吃");
}
public void call(){
System.out.println("叫");
}
abstract void exterior();
}
这样的类,就完成了我们想要的效果,不同的鸭子只要继承Duck,实现一下不一样的外观方法就可以了
但是,问题来了,现在我们知道,有的鸭子是会飞的,所有,我们需要给鸭子来添加飞的方法,但是要添加在哪里呢?
反例:添加给父类,造成的问题是如下,并不是所有的鸭子都会飞,橡皮鸭不会飞,我们要重写飞的方法来解决这个问题
class Test{
static abstract class Duck{
public void eat(){
System.out.println("吃");
}
public void call(){
System.out.println("叫");
}
public void fly(){
System.out.println("飞");
}
abstract void exterior();
}
class RedDuck extends Duck{
@Override
void exterior() {
System.out.println("红头");
}
}
class BlueDuck extends Duck{
@Override
void exterior() {
System.out.println("蓝头");
}
}
static class RubberDuck extends Duck{
@Override
public void fly() {
System.out.println("没飞起来");
}
@Override
void exterior() {
System.out.println("橡皮");
}
}
public static void main(String[] args) {
Duck r = new RubberDuck();
r.exterior();
r.eat();
r.call();
r.fly();
}
}
但是,这样写下去的话,我们每个不会飞的鸭子,都要去重写一下飞的方法,一旦变多,那就是程序员的噩梦
反例:我们想到了接口,采用接口完成,好了一些,但却仍然要不断地判断鸭子的状态需要实现哪些接口,并且方法没有重用性可言,每个实现接口的方法都要去完成方法体
class Test{
static abstract class Duck{
public void eat(){
System.out.println("吃");
}
abstract void exterior();
}
interface CallAble{
void call();
}
interface FlyAble{
void fly();
}
class RedDuck extends Duck implements CallAble,FlyAble{
@Override
void exterior() {
System.out.println("红头");
}
@Override
public void call() {
System.out.println("叫");
}
@Override
public void fly() {
System.out.println("飞");
}
}
class BlueDuck extends Duck implements CallAble,FlyAble{
@Override
public void call() {
System.out.println("叫");
}
@Override
public void fly() {
System.out.println("飞");
}
@Override
void exterior() {
System.out.println("蓝头");
}
}
static class RubberDuck extends Duck implements CallAble{
@Override
public void call() {
System.out.println("吱吱叫");
}
@Override
void exterior() {
System.out.println("橡皮");
}
}
public static void main(String[] args) {
RubberDuck r = new RubberDuck();
r.exterior();
r.eat();
r.fly();
r.call();
}
}
当然,jdk1.8中,接口可以添加默认实现
interface FlyAble{
default void fly(){
System.out.println("飞");
};
}
但这样还是没有解决根本问题,我们还是在重复判断鸭子状态的问题,去区分是否实现哪些接口,并要考虑是不是要去重写
正例:使用策略模式
class Test{
interface FlyBehavior{
void fly();
};
interface CallBehavior{
void call();
}
static class FlyWithWings implements FlyBehavior{
@Override
public void fly() {
System.out.println("用翅膀飞");
}
}
static class FlyWithKick implements FlyBehavior{
@Override
public void fly() {
System.out.println("踢飞");
}
}
static class Quack implements CallBehavior{
@Override
public void call() {
System.out.println("嘎嘎叫");
}
}
static class Squeak implements CallBehavior{
@Override
public void call() {
System.out.println("吱吱叫");
}
}
static abstract class Duck{
protected FlyBehavior flyBehavior;
protected CallBehavior callBehavior;
public void performFly(){
flyBehavior.fly();
}
public void performCall(){
callBehavior.call();
}
public void eat(){
System.out.println("会吃");
}
abstract void exterior();
}
static class RedDuck extends Duck{
public RedDuck() {
this.flyBehavior = new FlyWithWings();
this.callBehavior = new Quack();
}
@Override
void exterior() {
System.out.println("红头鸭");
}
}
class BlueDuck extends Duck{
public BlueDuck() {
this.flyBehavior = new FlyWithWings();
this.callBehavior = new Quack();
}
@Override
void exterior() {
System.out.println("蓝头鸭");
}
}
static class RubberDuck extends Duck{
public RubberDuck() {
this.flyBehavior = new FlyWithKick();
this.callBehavior = new Squeak();
}
@Override
void exterior() {
System.out.println("橡皮鸭");
}
}
public static void main(String[] args) {
RubberDuck r = new RubberDuck();
r.exterior();
r.eat();
r.performFly();
r.performCall();
RedDuck r2 = new RedDuck();
r2.exterior();
r2.eat();
r2.performFly();
r2.performCall();
}
}
我们用不同的模式去实现不同的行为,符合开闭原则的同时,我们使用时,通过父类去关联接口,子类构造器将接口判断行为分别实例化,这样就提高了不同行为的重用性,并且后续运行时,我们可以去通过set方法去实时的改变状态
运行时可以随时通过set方法改变状态,如下:
r.setFlyBehavior(new FlyWithWings());
r.performFly();
代理模式:为其他对象提供一种代理以控制对这个对象的访问
一个简单的加减乘除功能
class Test{
interface Calculator{
int add(int a,int b);
int sub(int a,int b);
int mul(int a,int b);
int div(int a,int b);
}
static class CalImpl implements Calculator{
@Override
public int add(int a, int b) {
return a+b;
}
@Override
public int sub(int a, int b) {
return a-b;
}
@Override
public int mul(int a, int b) {
return a*b;
}
@Override
public int div(int a, int b) {
return a/b;
}
}
public static void main(String[] args) {
CalImpl c = new CalImpl();
System.out.println(c.add(4,2));
System.out.println(c.sub(4,2));
System.out.println(c.mul(4,2));
System.out.println(c.div(4,2));
}
}
变化来了,要求为每个方法调用前后,都需要打印出一个日志信息
class Test{
interface Calculator{
int add(int a,int b);
int sub(int a,int b);
int mul(int a,int b);
int div(int a,int b);
}
static class CalImpl implements Calculator{
@Override
public int add(int a, int b) {
System.out.println("add方法开始!" +"a="+a+"b="+b);
int r = a+b;
System.out.println("add方法结束!" +"r="+r);
return r;
}
@Override
public int sub(int a, int b) {
System.out.println("sub方法开始!" +"a="+a+"b="+b);
int r = a-b;
System.out.println("sub方法结束!" +"r="+r);
return r;
}
@Override
public int mul(int a, int b) {
System.out.println("mul方法开始!" +"a="+a+"b="+b);
int r = a*b;
System.out.println("mul方法结束!" +"r="+r);
return r;
}
@Override
public int div(int a, int b) {
System.out.println("div方法开始!" +"a="+a+"b="+b);
int r = a/b;
System.out.println("div方法结束!" +"r="+r);
return r;
}
}
public static void main(String[] args) {
CalImpl c = new CalImpl();
System.out.println(c.add(4,2));
System.out.println(c.sub(4,2));
System.out.println(c.mul(4,2));
System.out.println(c.div(4,2));
}
}
我们发现,这样完成业务根本不是一个好办法,代码在重复,业务(加减)和非核心业务(打印日志)在不断的重复,需求如果变化要加入开方,求余的过程,或者,需要上午需要日志,下午不需要日志的需求,那我们就需要不断地改写这段代码
我们应该使用动态代理来完成
动态代理:在内存中写入的字节码,直接被类加载器使用
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
interface Calculator {
int add(int a, int b);
int sub(int a, int b);
int mul(int a, int b);
int div(int a, int b);
}
class CalImpl implements Calculator {
@Override
public int add(int a, int b) {
int r = a + b;
return r;
}
@Override
public int sub(int a, int b) {
int r = a - b;
return r;
}
@Override
public int mul(int a, int b) {
int r = a * b;
return r;
}
@Override
public int div(int a, int b) {
int r = a / b;
return r;
}
}
class MyHandler implements InvocationHandler {
private Calculator calculator;
public MyHandler(Calculator calculator) {
this.calculator = calculator;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName()+"开始,参数:"+ Arrays.toString(args));
System.out.println(method.getName()+"结束,结果是:"+method.invoke(calculator,args));
return 0;
}
}
public class AppTest {
public static void main(String[] args) {
Calculator calculator = new CalImpl();
ClassLoader classLoader = AppTest.class.getClassLoader();
//创建代理对象
Calculator cal = (Calculator) Proxy.newProxyInstance(classLoader, new Class[]{Calculator.class}, new MyHandler(calculator));
cal.add(3, 2);
cal.sub(3, 2);
cal.mul(3, 2);
cal.div(3, 2);
}
}
动态代理api
Proxy.newProxyInstance(classLoader, new Class[]{Calculator.class}, new MyHandler(calculator)
第一个参数:实例化一个对象,必然会调用类的构造器,运行第一次调用构造器,必定导致类的加载,而加载类的时候,就是jvm拿着classloader去加载类的字节码的,把字节码加载到内存中,才能进一步实例化对象
简单来说:只要实例化的对象,一定要加载类的字节码,加载字节码就一定要类的加载器。
使用动态代理的api实例化对象是一种不常用的方式,但这也是一种实例化,需要我们手动把类的加载器传入
第二个参数:实例化某一个类的对象,那是哪个类呢?这个类是在内存中,由动态代理生成的,这个对象会自动实现参数2的指定接口,生成的对象,必定能转成指定的接口类型。拥有接口中的方法
第三个参数:每次对动态代理对象方法的调用,都是一个假对象,虽然实现了接口方法,但没有任何内容,所以,调用方法会进入第三个参数的invoke方法中,第三个参数,是interfacehadle,通过类实现这个接口重写下面的方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
第一个参数:动态代理的对象
第二个参数:调用的接口方法
第三个参数:调用的接口方法的参数
}
动态代理具体步骤:
- 通过实现 InvocationHandler 接口创建自己的调用处理器;
- 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
- 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
- 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
观察者模式:被观察者发生变化时,所有被观察者都会通知
如题,我们先抽象出被观察者和观察者/** *抽象观察者 */ abstract class obServer { protected String name; public obServer(String name) { this.name = name; } protected SubJect subJect; //更新接口 public abstract void updata(); //不在观察 public abstract void dontLook(SubJect subJect); }
/**
*抽象被观察者
*/
abstract class SubJect{
//所有观察者
private List<obServer> obServerList=new ArrayList<>();
//状态
private int state;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
//添加观察者
public void attach(obServer obServer){
obServerList.add(obServer);
System.out.println("新增观察者:"+obServer.name);
}
//删除观察者
public void deletObServer(obServer obServer){
obServerList.remove(obServer);
System.out.println("移除观察者:"+obServer.name);
}
//更新观察者
public void updateObServer(){
for (obServer o:obServerList) {
o.updata();
}
}
}
有一档电视节目来当实体(被观察者)
class TVPlay extends SubJect{
private String theTVSet;
public String getTheTVSet() {
return theTVSet;
}
public void setTheTVSet(String theTVSet) {
this.theTVSet = theTVSet;
}
public void play(){
System.out.println("电视剧开播了!今日节目:"+ theTVSet);
//通知观察者
updateObServer();
}
}
人实体来充当观察者
class People extends obServer{
public People(String name) {
super(name);
}
@Override
public void updata() {
System.out.println(name+"收到电视剧开播通知");
}
@Override
public void dontLook(SubJect subJect) {
subJect.deletObServer(this);
}
}
执行时,被观察者会检查所有观察者,去调用play下发通知
public static void main(String[] args) {
TVPlay tvPlay = new TVPlay();
People man = new People("男人");
People woman = new People("女人");
tvPlay.attach(man);
tvPlay.attach(woman);
tvPlay.setTheTVSet("宫锁心玉");
tvPlay.play();
System.out.println("新的一期开始了!");
System.out.println("上次不好看,男人取消了观察");
man.dontLook(tvPlay);
tvPlay.setTheTVSet("宫锁心玉2");
tvPlay.play();
}
执行结果