学点设计模式,盘点Spring等源码中与设计模式的那些事之结构型模型
首先,我们先要了解结构型设计模式都包括哪些:适配器模式(Adapter)、桥接模式(Bridge)、过滤器模式(Filter)、组合模式(Composite)、装饰器模式(Decorator/Wrapper)、外观模式(Facade)、享元模式(Flyweight)、代理模式(Proxy)。
结构型模型主要关注怎样组合对象或者类,所以结构型模式又分为类结构模式(类的组合,继承)和对象结构模式(类与对象的组合,关联关系)。又因为合成复用原则告诉我们,要尽量使用关联关系代替继承关系,所以结构模型大多数都是对象结构模型。
1、适配器模式
- 定义:将一个接口转换成用户希望的另一个接口,使接口不兼容的类可以一起工作。
- 举例:日本人想要观看咱们中国的电影,我们不直接去修改电影播放器让其出现双语字母,而是新建一个适配器,让外国人使用适配器播放,适配器带有语言转换的能力。
对于这个场景,既可以使用类结构(继承)也可以使用对象结构(组合),代码没啥差别,下边采用对象结构实现
//播放器接口
public interface Player {
String play();
}
//翻译器接口
public interface Translator {
String translate(String content);
}
//播放器实现
public class MoviePlayer implements Player {
@Override
public String play() {
System.out.println("正在播放:java从入门到精通");
String content = "你好";
System.out.println(content);
return content;
}
}
//转换器实现
public class Zh_JPTranslator implements Translator{
@Override
public String translate(String content) {
if("你好".equals(content)){
return "空尼几哇";
}
if ("什么".equals(content)){
return "纳尼";
}
return "*******";
}
}
//播放器适配器
public class JPMoviePlayerAdapter implements Player {
//组合的方式
private Translator translator = new Zh_JPTranslator();
private Player target;//被适配对象
public JPMoviePlayerAdapter(Player target){
this.target = target;
}
@Override
public String play() {
String play = target.play();
//转换字幕
String translate = translator.translate(play);
System.out.println("日文:"+translate);
return play;
}
}
- 核心:系统中所有原有的东西都不能修改,扩展一个新的类,来连接两个之前不同的类。
2、桥接模式
-
定义:将抽象与实现解耦,使两者都可以独立变化(真正引起类变化的维度抽取出来),桥接将继承转为关联,降低类之间的耦合度,减少代码量。
-
举个栗子:有两种手机,拍照手机和性能手机,两种手机在线上和线下的价格都不同,考虑这个类里边的一些东西会扩展很多,就应该把这个东西分离出来。
-
代码示例:
/**
* 1、抽象手机类
* 手机有各种销售渠道价格都不一样
*/
public abstract class AbstractPhone {
//【真正会引起此类变化的一个维度直接抽取出来,通过组合的方式接起来】
AbstractSale sale; //分离渠道【桥接的关注点】
//当前手机的描述
abstract String getPhone();
public void setSale(AbstractSale sale) {
this.sale = sale;
}
}
/**
* 抽象销售渠道
*/
public abstract class AbstractSale {
private String type;
private Integer price;
public AbstractSale(String type,Integer price){
this.type = type;
this.price = price;
}
String getSaleInfo(){
return "渠道:"+type+"==>"+"价格:"+price;
}
}
//哪种手机,根据手机种类实现......多个
public class PhotoPhone extends AbstractPhone{
@Override
String getPhone() {
return "PhotoPhone:"+sale.getSaleInfo();
}
}
/**
* 线上渠道,根据渠道实现......多个
*/
public class OnlineSale extends AbstractSale{
public OnlineSale(String type, Integer price) {
super(type, price);
}
}
//TestClass
public class MainTest {
public static void main(String[] args) {
//拍照手机的线上渠道
PhotoPhone iPhone = new PhotoPhone();
iPhone.setSale(new OnlineSale("线上",2599));
String phone = iPhone.getPhone();
System.out.println(phone);
}
}
3、装饰器模式
- 定义:创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
- 对比适配器模式:适配器是连接两个类,可以增强一个类;装饰器是只增强一个类不用连接两个类。
- 代码示例
//附一个网上很常见的例子,给图像增加颜色
//抽象的图像
public interface Shape {
void draw();
}
//具体的图像:圆......可以多个,这里只示例一个
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Shape: Circle");
}
}
//抽象的装饰类
public abstract class ShapeDecorator implements Shape {
protected Shape decoratedShape;
public ShapeDecorator(Shape decoratedShape){
this.decoratedShape = decoratedShape;
}
public void draw(){
decoratedShape.draw();
}
}
//具体的装饰类
public class RedShapeDecorator extends ShapeDecorator {
public RedShapeDecorator(Shape decoratedShape) {
super(decoratedShape);
}
@Override
public void draw() {
decoratedShape.draw();
setRedBorder(decoratedShape);
}
private void setRedBorder(Shape decoratedShape){
System.out.println("Border Color: Red");
}
}
4、代理模式
-
定义:给某一个对象提供一个代理,并由代理对象控制对原对象的引用。
-
代理模式又分为静态代理和动态代理,静态代理和装饰器十分相似,所以大家经常认为装饰器是代理的一种。
下面来看一下动态代理
- jdk动态代理示例
//一个抖音直播例子
//首先,来两个抽象接口,因为JDK要求被代理对象必须有接口
//抽象的跳舞接口
public interface DanceTikTok {
void dance();
}
//抽像的卖货接口
public interface SellTikTok {
void sell();
}
//实现这两个接口
public class MyTikTok implements DanceTikTok,SellTikTok {
@Override
public void dance() {
System.out.println("跳个舞......");
}
@Override
public void sell() {
System.out.println("3,2,1 上车......");
}
}
//动态代理核心类,万能代理类
public class JdkTiktokProxy<T> implements InvocationHandler {
private T target;
//接受被代理对象
JdkTiktokProxy(T target){
this.target = target;
}
//获取被代理对象的 代理对象
public static<T> T getProxy(T t) {
/**
* ClassLoader loader, 当前被代理对象的类加载器
* Class<?>[] interfaces, 当前被代理对象所实现的所有接口
* InvocationHandler h,
* 当前被代理对象执行目标方法的时候我们使用h可以定义拦截增强方法
*/
Object o = Proxy.newProxyInstance(
t.getClass().getClassLoader(),
t.getClass().getInterfaces(), //必须接口
new JdkTiktokProxy(t));
return (T)o;
}
//定义目标方法的拦截逻辑;每个方法都会进来的
@Override
public Object invoke(Object proxy,
Method method,
Object[] args) throws Throwable {
//反射执行
System.out.println("真正执行被代理对象的方法");
Object invoke = method.invoke(target, args);
System.out.println("执行完返回......");
return invoke;
}
}
-
cglib动态代理(使用cglib时记得加依赖)
-
cglib在底层通过直接操作字节码的方式,使用继承的方式得到目标对象方法。
-
代码演示:
//要被代理增强的类
public class MyTikTok {
public void tiktok() {
System.out.println("开始直播....");
}
}
//cglib核心代码
public class CglibProxy {
//为任意对象创建代理
public static<T> T createProxy(T t){
//1、创建一个增强器
Enhancer enhancer = new Enhancer();
//2、设置要增强哪个个类的功能。增强器为这个类动态创建一个子类
enhancer.setSuperclass(t.getClass());
//3、设置回调
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj,
Method method,
Object[] args,
MethodProxy proxy) throws Throwable {
//编写拦截的逻辑
System.out.println("cglib开场秀 .......");
//目标方法进行执行
Object invoke = proxy.invokeSuper(obj,args);
return invoke;
}
});
Object o = enhancer.create();
return (T) o;
}
}
//测试
public class CglibTest {
public static void main(String[] args) {
MyTikTok tikTok = new MyTikTok();
MyTikTok proxy = CglibProxy.createProxy(tikTok);
proxy.tiktok();
}
}
5、组合模式
- 定义:把一组相似的对象当作一个单一的对象。将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
//部分代码展示
public class Menu {
private Integer id;
private String name;
public Menu(Integer id,String name){
this.id = id;
this.name = name;
}
//组合模式关注点
private List<Menu> childs = new ArrayList<>();
//提供添加层级的方法
void addChildMenu(Menu menu){
childs.add(menu);
}
//层级遍历方法
void printMenu(){
System.out.println(name);
if(childs.size() > 0){
for (Menu child : childs) {
child.printMenu();
}
}
}
}
- 组合模式的优缺点:
- 优点:1、高层模块调用简单。 2、节点自由增加。
- 缺点:在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。
6、外观模式
- 定义:通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问。
- 举例:比如你现在想在一个城市定居落户,你需要去公安局办理身份证,监狱局认证学籍,社保局交社保,来回跑比较麻烦,这时政府推出了一个微信小程序,在小程序上就可以办理完所有的事。
//办一个新身份证
public class Police {
public void resgister(String name){
System.out.println(name + "办一个新身份证");
}
}
//存学籍档案
public class Edu {
public void school(String name){
System.out.println(name+"存学籍档案");
}
}
//开通当地社保
public class Social {
public void handleSocial(String name){
System.out.println(name+"开通当地社保");
}
}
//落户小程序
public class WeiXinFacade {
Police police = new Police();
Edu edu = new Edu();
Social social = new Social();
public void handleXxx(String name){
police.resgister(name);
edu.school(name);
social.handleSocial(name);
}
}
//Test......
7、享元模式
- 定义:现在有很多对象需要大量重复的使用,考虑到使用池化技术,享元模式就是来告诉我们使用池化技术应该怎么考虑的。
- 举例:比如一个店里有很多的服务员,如果某个服务员处于空闲状态,那么我们每个人都可以叫这些服务员为我们服务。
/**
* 可共享和不可共享状态
*/
public abstract class AbstractWaitressFlyweight {
boolean canService = true;//能否服务
//正在服务。 享元的不可共享属性留给外部进行改变的接口
abstract void service();
//服务完成。 享元的不可共享属性留给外部进行改变的接口
abstract void end();
public boolean isCanService() {
return canService;
}
}
/**
* 具体享元类
*/
@AllArgsConstructor
public class BeautifulWaitress extends AbstractWaitressFlyweight{
String id;//工号
String name;//名字
int age;//年龄
//以上是不变的
@Override
void service() {
System.out.println("工号:"+id+";"+name+" "+age+" 正在为您服务...");
//改变外部状态
this.canService = false;
}
@Override
void end() {
System.out.println("工号:"+id+";"+name+" "+age+" 服务结束...请给五星好评");
this.canService = true;
}
}
//享元工厂
public class Shop {
private static Map<String,AbstractWaitressFlyweight> pool = new HashMap<>();
//享元,池子中有对象
static {
BeautifulWaitress waitress =
new BeautifulWaitress("1111","张三",18);
BeautifulWaitress waitress2 =
new BeautifulWaitress("9527","李四",20);
pool.put(waitress.id,waitress);
pool.put(waitress2.id,waitress2);
}
public static AbstractWaitressFlyweight getWaitress(String name){
AbstractWaitressFlyweight flyweight = pool.get(name);
if(flyweight == null){
for (AbstractWaitressFlyweight value : pool.values()) {
//当前共享对象能否服务
if(value.isCanService()){
return value;
}
};
return null;
}
return flyweight;
}
}
//Test......
- 享元模式优缺点
- 优点:大大减少对象的创建,降低系统的内存,使效率提高。
- 缺点:提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。
结构型模型到这里就学习完了,下一篇:行为型模型…