设计模式(二)
作者:互联网
十、代理模式 Proxy
- 静态代理
- 动态代理
- Sping AOP
import java.util.Random;
/**
* 问题:我想记录坦克的移动时间
* 最简单的办法:修改代码,记录时间
* 问题2:如果无法改变方法源码呢?
* 用继承?
* v05:使用代理
*/
public class Tank implements Movable {
/**
* 模拟坦克移动了一段儿时间
*/
@Override
public void move() {
System.out.println("Tank moving claclacla...");
try {
Thread.sleep(new Random().nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new TankTimeProxy(new Tank()).move();
}
}
class TankTimeProxy implements Movable {
Tank tank;
public TankTimeProxy(Tank tank) {
this.tank = tank;
}
@Override
public void move() {
long start = System.currentTimeMillis();
tank.move();
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
interface Movable {
void move();
}
import java.util.Random;
/**
* 问题:我想记录坦克的移动时间
* 最简单的办法:修改代码,记录时间
* 问题2:如果无法改变方法源码呢?
* 用继承?
* v05:使用代理
* v06:代理有各种类型
* 问题:如何实现代理的各种组合?继承?Decorator?
*/
public class Tank implements Movable {
/**
* 模拟坦克移动了一段儿时间
*/
@Override
public void move() {
System.out.println("Tank moving claclacla...");
try {
Thread.sleep(new Random().nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new TankTimeProxy().move();
}
}
class TankTimeProxy implements Movable {
Tank tank;
@Override
public void move() {
long start = System.currentTimeMillis();
tank.move();
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
//日志代理
class TankLogProxy implements Movable {
Tank tank;
@Override
public void move() {
System.out.println("start moving...");
tank.move();
System.out.println("stopped!");
}
}
interface Movable {
void move();
}
- 静态代理
import java.util.Random;
/**
* 问题:我想记录坦克的移动时间
* 最简单的办法:修改代码,记录时间
* 问题2:如果无法改变方法源码呢?
* 用继承?
* v05:使用代理
* v06:代理有各种类型
* 问题:如何实现代理的各种组合?继承?Decorator?
* v07:代理的对象改成Movable类型-越来越像decorator了
*
*/
public class Tank implements Movable {
/**
* 模拟坦克移动了一段儿时间
*/
@Override
public void move() {
System.out.println("Tank moving claclacla...");
try {
Thread.sleep(new Random().nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Tank t = new Tank();
TankTimeProxy ttp = new TankTimeProxy(t);
TankLogProxy tlp = new TankLogProxy(ttp);
tlp.move();
// new TankLogProxy(
// new TankTimeProxy(
// new Tank()
// )
// ).move();
}
}
class TankTimeProxy implements Movable {
Movable m;
public TankTimeProxy(Movable m) {
this.m = m;
}
@Override
public void move() {
long start = System.currentTimeMillis();
m.move();
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
class TankLogProxy implements Movable {
Movable m;
public TankLogProxy(Movable m) {
this.m = m;
}
@Override
public void move() {
System.out.println("start moving...");
m.move();
long end = System.currentTimeMillis();
System.out.println("stopped!");
}
}
interface Movable {
void move();
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Random;
/**
* 问题:我想记录坦克的移动时间
* 最简单的办法:修改代码,记录时间
* 问题2:如果无法改变方法源码呢?
* 用继承?
* v05:使用代理
* v06:代理有各种类型
* 问题:如何实现代理的各种组合?继承?Decorator?
* v07:代理的对象改成Movable类型-越来越像decorator了
* v08:如果有stop方法需要代理...
* 如果想让LogProxy可以重用,不仅可以代理Tank,还可以代理任何其他可以代理的类型 Object
* (毕竟日志记录,时间计算是很多方法都需要的东西),这时该怎么做呢?
* 分离代理行为与被代理对象
* 使用jdk的动态代理
*/
public class Tank implements Movable {
/**
* 模拟坦克移动了一段儿时间
*/
@Override
public void move() {
System.out.println("Tank moving claclacla...");
try {
Thread.sleep(new Random().nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Tank tank = new Tank();
//reflection 通过二进制字节码分析类的属性和方法
//反射
Movable m = (Movable)Proxy.newProxyInstance(Tank.class.getClassLoader(),
new Class[]{Movable.class}, //tank.class.getInterfaces()
new LogHander(tank)
);
m.move();
}
}
class LogHander implements InvocationHandler {
Tank tank;
public LogHander(Tank tank) {
this.tank = tank;
}
//getClass.getMethods[]
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("method " + method.getName() + " start..");
Object o = method.invoke(tank, args);
System.out.println("method " + method.getName() + " end!");
return o;
}
}
interface Movable {
void move();
}
- jdk实现动态代理,底层是ASM
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Random;
/**
* 问题:我想记录坦克的移动时间
* 最简单的办法:修改代码,记录时间
* 问题2:如果无法改变方法源码呢?
* 用继承?
* v05:使用代理
* v06:代理有各种类型
* 问题:如何实现代理的各种组合?继承?Decorator?
* v07:代理的对象改成Movable类型-越来越像decorator了
* v08:如果有stop方法需要代理...
* 如果想让LogProxy可以重用,不仅可以代理Tank,还可以代理任何其他可以代理的类型
* (毕竟日志记录,时间计算是很多方法都需要的东西),这时该怎么做呢?
* 分离代理行为与被代理对象
* 使用jdk的动态代理
*
* v09: 横切代码与业务逻辑代码分离 AOP
*/
public class Tank implements Movable {
/**
* 模拟坦克移动了一段儿时间
*/
@Override
public void move() {
System.out.println("Tank moving claclacla...");
try {
Thread.sleep(new Random().nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Tank tank = new Tank();
Movable m = (Movable)Proxy.newProxyInstance(Tank.class.getClassLoader(),
new Class[]{Movable.class}, //tank.class.getInterfaces()
new TimeProxy(tank)
);
m.move();
}
}
class TimeProxy implements InvocationHandler {
Movable m;
public TimeProxy(Movable m) {
this.m = m;
}
public void before() {
System.out.println("method start..");
}
public void after() {
System.out.println("method stop..");
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object o = method.invoke(m, args);
after();
return o;
}
}
interface Movable {
void move();
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Random;
/**
* 问题:我想记录坦克的移动时间
* 最简单的办法:修改代码,记录时间
* 问题2:如果无法改变方法源码呢?
* 用继承?
* v05:使用代理
* v06:代理有各种类型
* 问题:如何实现代理的各种组合?继承?Decorator?
* v07:代理的对象改成Movable类型-越来越像decorator了
* v08:如果有stop方法需要代理...
* 如果想让LogProxy可以重用,不仅可以代理Tank,还可以代理任何其他可以代理的类型
* (毕竟日志记录,时间计算是很多方法都需要的东西),这时该怎么做呢?
* 分离代理行为与被代理对象
* 使用jdk的动态代理
*
* v09: 横切代码与业务逻辑代码分离 AOP
* v10: 通过反射观察生成的代理对象
* jdk反射生成代理必须面向接口,这是由Proxy的内部实现决定的
*/
public class Tank implements Movable {
/**
* 模拟坦克移动了一段儿时间
*/
@Override
public void move() {
System.out.println("Tank moving claclacla...");
try {
Thread.sleep(new Random().nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Tank tank = new Tank();
System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles","true");
Movable m = (Movable)Proxy.newProxyInstance(Tank.class.getClassLoader(),
new Class[]{Movable.class}, //tank.class.getInterfaces()
new TimeProxy(tank)
);
m.move();
}
}
class TimeProxy implements InvocationHandler {
Movable m;
public TimeProxy(Movable m) {
this.m = m;
}
public void before() {
System.out.println("method start..");
}
public void after() {
System.out.println("method stop..");
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//Arrays.stream(proxy.getClass().getMethods()).map(Method::getName).forEach(System.out::println);
before();
Object o = method.invoke(m, args);
after();
return o;
}
}
interface Movable {
void move();
}
jdk动态代理
Cglib实现:
- 注意:maven要加cglib依赖
- 原理:生成被代理类的一个子类,不能代理final类;但是ASM可以,因为ASM是直接操作二级制码;cglib的底层原理也是ASM
/**
* CGLIB实现动态代理不需要接口
*/
public class Main {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Tank.class);
enhancer.setCallback(new TimeMethodInterceptor());
Tank tank = (Tank)enhancer.create();
tank.move();
}
}
class TimeMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println(o.getClass().getSuperclass().getName());
System.out.println("before");
Object result = null;
result = methodProxy.invokeSuper(o, objects);
System.out.println("after");
return result;
}
}
class Tank {
public void move() {
System.out.println("Tank moving claclacla...");
try {
Thread.sleep(new Random().nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
spring AOP
<bean id="tank" class="com.mashibing.dp.spring.v1.Tank"/>
<bean id="timeProxy" class="com.mashibing.dp.spring.v1.TimeProxy"/>
<aop:config>
<aop:aspect id="time" ref="timeProxy">
<aop:pointcut id="onmove" expression="execution(void com.mashibing.dp.spring.v1.Tank.move())"/>
<aop:before method="before" pointcut-ref="onmove"/>
<aop:after method="after" pointcut-ref="onmove"/>
</aop:aspect>
</aop:config>
/**
* spring aop test
*/
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("app.xml");
Tank t = (Tank)context.getBean("tank");
t.move();
}
}
import java.util.Random;
/**
*
*/
public class Tank {
/**
* 模拟坦克移动了一段儿时间
*/
public void move() {
System.out.println("Tank moving claclacla...");
try {
Thread.sleep(new Random().nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class TimeProxy {
public void before() {
System.out.println("method start.." + System.currentTimeMillis());
}
public void after() {
System.out.println("method stop.." + System.currentTimeMillis());
}
}
- 注解
/**
* spring aop test
*/
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("app_auto.xml");
Tank t = (Tank)context.getBean("tank");
t.move();
}
}
import java.util.Random;
/**
*
*/
public class Tank {
/**
* 模拟坦克移动了一段儿时间
*/
public void move() {
System.out.println("Tank moving claclacla...");
try {
Thread.sleep(new Random().nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class TimeProxy {
@Before("execution (void com.mashibing.dp.spring.v2.Tank.move())")
public void before() {
System.out.println("method start.." + System.currentTimeMillis());
}
@After("execution (void com.mashibing.dp.spring.v2.Tank.move())")
public void after() {
System.out.println("method stop.." + System.currentTimeMillis());
}
}
十一、迭代器模式 Iterator
容器与容器遍历
- 数组实现
/**
* 构建一个容器,可以添加对象
*/
public class Main {
public static void main(String[] args) {
ArrayList_ list = new ArrayList_();
for(int i=0; i<15; i++) {
list.add(new String("s" + i));
}
System.out.println(list.size());
}
}
/**
* 相比数组,这个容器不用考虑边界问题,可以动态扩展
*/
class ArrayList_ {
Object[] objects = new Object[10];
//objects中下一个空的位置在哪儿,或者说,目前容器中有多少个元素
private int index = 0;
public void add(Object o) {
if(index == objects.length) {
Object[] newObjects = new Object[objects.length*2];
System.arraycopy(objects, 0, newObjects, 0, objects.length);
objects = newObjects;
}
objects[index] = o;
index ++;
}
public int size() {
return index;
}
}
-链表实现
/**
* v1:构建一个容器,可以添加对象
* v2:用链表来实现一个容器
*/
public class Main {
public static void main(String[] args) {
LinkedList_ list = new LinkedList_();
for(int i=0; i<15; i++) {
list.add(new String("s" + i));
}
System.out.println(list.size());
}
}
/**
* 相比数组,这个容器不用考虑边界问题,可以动态扩展
*/
class LinkedList_ {
Node head = null;
Node tail = null;
//目前容器中有多少个元素
private int size = 0;
public void add(Object o) {
Node n = new Node(o);
n.next = null;
if(head == null) {
head = n;
tail = n;
}
tail.next = n;
tail = n;
size++;
}
private class Node {
private Object o;
Node next;
public Node(Object o) {
this.o = o;
}
}
public int size() {
return size;
}
}
- Iterator_ 接口
public interface Iterator_ {
boolean hasNext();
Object next();
}
public interface Collection_ {
void add(Object o);
int size();
Iterator_ iterator();
}
/**
* 相比数组,这个容器不用考虑边界问题,可以动态扩展
*/
class ArrayList_ implements Collection_ {
Object[] objects = new Object[10];
//objects中下一个空的位置在哪儿,或者说,目前容器中有多少个元素
private int index = 0;
public void add(Object o) {
if(index == objects.length) {
Object[] newObjects = new Object[objects.length*2];
System.arraycopy(objects, 0, newObjects, 0, objects.length);
objects = newObjects;
}
objects[index] = o;
index ++;
}
@Override
public int size() {
return index;
}
@Override
public Iterator_ iterator() {
return new ArrayListIterator();
}
private class ArrayListIterator implements Iterator_{
private int currentIndex = 0;
@Override
public boolean hasNext() {
if(currentIndex >= index) return false;
return true;
}
@Override
public Object next() {
Object o = objects[currentIndex];
currentIndex ++;
return o;
}
}
}
- JDK的容器的Iterator
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/**
* v1:构建一个容器,可以添加对象
* v2:用链表来实现一个容器
* v3:添加容器的共同接口,实现容器的替换
* v4:如何对容器遍历呢?
* v4:用一种统一的遍历方式,要求每一个容器都要提供Iterator的实现类
* 作业:实现LinkedList的Iterator
* v6:JDK的容器的Iterator
*/
public class Main {
public static void main(String[] args) {
Collection c = new ArrayList();
for(int i=0; i<15; i++) {
c.add(new String("s" + i));
}
Iterator it = c.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
}
}
- 终版
/**
* 相比数组,这个容器不用考虑边界问题,可以动态扩展
*/
class ArrayList_<E> implements Collection_<E> {
E[] objects = (E[])new Object[10];
//objects中下一个空的位置在哪儿,或者说,目前容器中有多少个元素
private int index = 0;
public void add(E o) {
if(index == objects.length) {
E[] newObjects = (E[])new Object[objects.length*2];
System.arraycopy(objects, 0, newObjects, 0, objects.length);
objects = newObjects;
}
objects[index] = o;
index ++;
}
public int size() {
return index;
}
@Override
public Iterator_<E> iterator() {
return new ArrayListIterator();
}
private class ArrayListIterator<E> implements Iterator_<E> {
private int currentIndex = 0;
@Override
public boolean hasNext() {
if(currentIndex >= index) return false;
return true;
}
@Override
public E next() {
E o = (E)objects[currentIndex];
currentIndex ++;
return o;
}
}
}
public interface Collection_<E> {
void add(E o);
int size();
Iterator_ iterator();
}
public interface Iterator_<E> { //Element //Type //K //Value V Tank
boolean hasNext();
E next(); //Tank next() Iterator_<Tank> it = ... Tank t = it.next();
}
/**
* 相比数组,这个容器不用考虑边界问题,可以动态扩展
*/
class LinkedList_ implements Collection_ {
Node head = null;
Node tail = null;
//目前容器中有多少个元素
private int size = 0;
public void add(Object o) {
Node n = new Node(o);
n.next = null;
if(head == null) {
head = n;
tail = n;
}
tail.next = n;
tail = n;
size++;
}
private class Node {
private Object o;
Node next;
public Node(Object o) {
this.o = o;
}
}
public int size() {
return size;
}
@Override
public Iterator_ iterator() {
return null;
}
}
/**
* 相比数组,这个容器不用考虑边界问题,可以动态扩展
*/
class ArrayList_<E> implements Collection_<E> {
E[] objects = (E[])new Object[10];
//objects中下一个空的位置在哪儿,或者说,目前容器中有多少个元素
private int index = 0;
public void add(E o) {
if(index == objects.length) {
E[] newObjects = (E[])new Object[objects.length*2];
System.arraycopy(objects, 0, newObjects, 0, objects.length);
objects = newObjects;
}
objects[index] = o;
index ++;
}
public int size() {
return index;
}
@Override
public Iterator_<E> iterator() {
return new ArrayListIterator();
}
private class ArrayListIterator<E> implements Iterator_<E> {
private int currentIndex = 0;
@Override
public boolean hasNext() {
if(currentIndex >= index) return false;
return true;
}
@Override
public E next() {
E o = (E)objects[currentIndex];
currentIndex ++;
return o;
}
}
}
/**
* v1:构建一个容器,可以添加对象
* v2:用链表来实现一个容器
* v3:添加容器的共同接口,实现容器的替换
* v4:如何对容器遍历呢?
* v4:用一种统一的遍历方式,要求每一个容器都要提供Iterator的实现类
* 作业:实现LinkedList的Iterator
* v6:JDK的容器实现
* v7:实现泛型版本
*/
public class Main {
public static void main(String[] args) {
Collection_<String> list = new ArrayList_<>();
for(int i=0; i<15; i++) {
list.add(new String("s" + i));
}
System.out.println(list.size());
//这个接口的调用方式:
Iterator_<String> it = list.iterator();
while(it.hasNext()) {
String o = it.next();
System.out.println(o);
}
}
}
十二、访问者模式 Visitor
在结构不变
的情况下,动态改变对于内部元素的动作
public class Computer {
ComputerPart cpu = new CPU();
ComputerPart memory = new Memory();
ComputerPart board = new Board();
public void acccept(Visitor v) {
this.cpu.accept(v);
this.memory.accept(v);
this.board.accept(v);
}
public static void main(String[] args) {
PersonelVisitor p = new PersonelVisitor();
new Computer().acccept(p);
System.out.println(p.totalPrice);
}
}
abstract class ComputerPart {
abstract void accept(Visitor v);
//some other operations eg:getName getBrand
abstract double getPrice();
}
class CPU extends ComputerPart {
@Override
void accept(Visitor v) {
v.visitCpu(this);
}
@Override
double getPrice() {
return 500;
}
}
class Memory extends ComputerPart {
@Override
void accept(Visitor v) {
v.visitMemory(this);
}
@Override
double getPrice() {
return 300;
}
}
class Board extends ComputerPart {
@Override
void accept(Visitor v) {
v.visitBoard(this);
}
@Override
double getPrice() {
return 200;
}
}
interface Visitor {
void visitCpu(CPU cpu);
void visitMemory(Memory memory);
void visitBoard(Board board);
}
class PersonelVisitor implements Visitor {
double totalPrice = 0.0;
@Override
public void visitCpu(CPU cpu) {
totalPrice += cpu.getPrice()*0.9;
}
@Override
public void visitMemory(Memory memory) {
totalPrice += memory.getPrice()*0.85;
}
@Override
public void visitBoard(Board board) {
totalPrice += board.getPrice()*0.95;
}
}
class CorpVisitor implements Visitor {
double totalPrice = 0.0;
@Override
public void visitCpu(CPU cpu) {
totalPrice += cpu.getPrice()*0.6;
}
@Override
public void visitMemory(Memory memory) {
totalPrice += memory.getPrice()*0.75;
}
@Override
public void visitBoard(Board board) {
totalPrice += board.getPrice()*0.75;
}
}
十三、建造者模式 Builder
构建复杂对象
- 分离复杂对象的构建和表示
- 同样的构建过程可以创建不同的表示
- 无需记忆,自然使用
public class Terrain {
Wall w;
Fort f;
Mine m;
}
class Wall {
int x, y, w, h;
public Wall(int x, int y, int w, int h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
}
class Fort {
int x, y, w, h;
public Fort(int x, int y, int w, int h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
}
class Mine {
int x, y, w, h;
public Mine(int x, int y, int w, int h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
}
public interface TerrainBuilder {
TerrainBuilder buildWall();
TerrainBuilder buildFort();
TerrainBuilder buildMine();
Terrain build();
}
public class ComplexTerrainBuilder implements TerrainBuilder {
Terrain terrain = new Terrain();
@Override
public TerrainBuilder buildWall() {
terrain.w = new Wall(10, 10, 50, 50);
return this;
}
@Override
public TerrainBuilder buildFort() {
terrain.f = new Fort(10, 10, 50, 50);
return this;
}
@Override
public TerrainBuilder buildMine() {
terrain.m = new Mine(10, 10, 50, 50);
return this;
}
@Override
public Terrain build() {
return terrain;
}
}
public class Main {
public static void main(String[] args) {
TerrainBuilder builder = new ComplexTerrainBuilder();
Terrain t = builder.buildFort().buildMine().buildWall().build();
//new Terrain(Wall w, Fort f, Mine m)
//Effective Java
Person p = new Person.PersonBuilder()
.basicInfo(1, "zhangsan", 18)
//.score(20)
.weight(200)
//.loc("bj", "23")
.build();
}
}
public class Person {
int id;
String name;
int age;
double weight;
int score;
Location loc;
private Person() {}
public static class PersonBuilder {
Person p = new Person();
public PersonBuilder basicInfo(int id, String name, int age) {
p.id = id;
p.name = name;
p.age = age;
return this;
}
public PersonBuilder weight(double weight) {
p.weight = weight;
return this;
}
public PersonBuilder score(int score) {
p.score = score;
return this;
}
public PersonBuilder loc(String street, String roomNo) {
p.loc = new Location(street, roomNo);
return this;
}
public Person build() {
return p;
}
}
}
class Location {
String street;
String roomNo;
public Location(String street, String roomNo) {
this.street = street;
this.roomNo = roomNo;
}
}
十四、适配器模式 Adapter(Wrapper)
- java.io
- jdbc-odbc bridge(不是桥接模式)
- ASM transformer
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
public class Main {
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("c:/test.text");
InputStreamReader isr = new InputStreamReader(fis);
//字节流不能直接读String, 中间使用InputSteam进行适配
BufferedReader br = new BufferedReader(isr);
String line = br.readLine();
while (line != null && !line.equals("")) {
System.out.println(line);
}
br.close();
}
}
- 误区:
- 常见的Adapter类反而不是Adapter
- windowAdpter(一个抽象类写了很多空方法,只需要重写自己想要的那个类即可,啥模式都不是)
- KeyAdapetyter
标签:Tank,void,System,class,new,设计模式,public 来源: https://blog.csdn.net/weixin_43758633/article/details/116245503