设计模式是思想,灵活使用会很大程度上减少开发的难度,拓展框架和程序的时候会很方便
23种设计模式中的结构模式有7种:适配器模式、装饰模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
下面就来依次分享讲解各种结构模式。
1适配器模式
适配器模式有点像是转换器,将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题。主要分为三类:类的适配器模式、对象的适配器模式、接口的适配器模式。
1.1类的适配器模式(Adapter)
核心思想就是:有一个“视频“类,拥有一个方法,待适配,目标接口是“手机“,通过“智能手机“类,将“视频“的功能扩展到“手机“里面里,看代码:
public class Video {
public void playVideo(){
System.out.println("视频播放");
}
}
public interface Phone {
public void playVideo();
public void call();
}
public class IntelligentPhone extends Video implements Phone {
@Override
public void call(){
System.out.println("打电话");
}
}
//测试类
public class AdapterTest {
public static void main(String[] args){
Phone phone = new IntelligentPhone();
phone.playVideo();
phone.call();
}
}
//输出:
//视频播放
//打电话
这样IntelligentPhone接口的实现类就具有了Video类的功能。
使用一个适配器类来继承目标类来实现适配。
1.2对象的适配器模式
基本思路和类的适配器模式相同,只是将IntelligentPhone类作修改,这次不继承Video类,而是持有Video类的实例,以达到解决兼容性的问题。
public class IntelligentPhone implements Phone {
private Video video;
public IntelligentPhone(Video video){
super();
this.video = video;
}
@Override
public void call(){
System.out.println("打电话");
}
@Override
public void playVideo(){
video.playVideo();
}
}
//测试类
public class AdapterTest {
public static void main(String[] args){
Video video = new Video();
Phone phone = new IntelligentPhone(video);
phone.playVideo();
phone.call();
}
}
//输出:
//视频播放
//打电话
适配器的方法和思路不同,但是输出结果是一样的,对象的适配器模式是适配类持有对象(适配的时候NEW对象出来),类的适配器模式(是继承目标类)。
1.3接口的适配器模式
接口的适配器是这样的:有时我们写的一个接口中有多个抽象方法,当我们写该接口的实现类时,必须实现该接口的所有方法,这明显有时比较浪费,因为并不是所有的方法都是我们需要的,有时只需要某一些,此处为了解决这个问题,我们引入了接口的适配器模式,借助于一个抽象类,该抽象类实现了该接口,实现了所有的方法,而我们不和原始的接口打交道,只和该抽象类取得联系,所以我们写一个类,继承该抽象类,重写我们需要的方法就行。
//还是手机的例子
public interface Phone {
public void playVideo();
public void call();
public void playMusic();
}
//适配抽象类 实现手机接口
public abstract class PhoneAbs implements Phone {
@Override
public void playVideo(){
System.out.println("看视频");
}
@Override
public void call(){
System.out.println("打电话");
}
@Override
public void playMusic(){
System.out.println("放音乐");
}
}
//实际类 继承适配的抽象类 实现需要的方法
public class VideoPhone extends PhoneAbs {
public void playVideo(){
System.out.println("看视频");
}
public void call(){
System.out.println("打电话");
}
}
public class MusicPhone extends PhoneAbs {
public void call(){
System.out.println("打电话");
}
public void playMusic(){
System.out.println("放音乐");
}
}
总结一下三种适配模式:
类的适配器模式:当希望将一个类转换成满足另一个新接口的类时,可以使用类的适配器模式,创建一个新类,继承原有的类,实现新的接口即可。
对象的适配器模式:当希望将一个对象转换成满足另一个新接口的对象时,可以创建一个适配类,持有原类的一个实例,在适配类的方法中,调用实例的方法就行。
接口的适配器模式:当不希望实现一个接口中所有的方法时,可以创建一个抽象类适配类,实现所有方法,我们写别的类的时候,继承抽象类即可。
2装饰模式
说到装饰模式,就不得不讲一下代理模式。这两个设计模式看起来很像。对装饰器模式来说,装饰者(decorator)和被装饰者(decoratee)都实现同一个 接口。对代理模式来说,代理类(proxy class)和真实处理的类(real class)都实现同一个接口。此外,不论我们使用哪一个模式,都可以很容易地在真实对象的方法前面或者后面加上自定义的方法。
然而,实际上,在装饰器模式和代理模式之间还是有很多差别的。装饰器模式关注于在一个对象上动态的添加方法,然而代理模式关注于控制对对象的访问。换句话 说,用代理模式,代理类(proxy class)可以对它的客户隐藏一个对象的具体信息。因此,当使用代理模式的时候,我们常常在一个代理类中创建一个对象的实例。并且,当我们使用装饰器模 式的时候,我们通常的做法是将原始对象作为一个参数传给装饰者的构造器。
装饰器模式的应用场景:
1、需要扩展一个类的功能。
2、动态的为一个对象增加功能,而且还能动态撤销。(继承不能做到这一点,继承的功能是静态的,不能动态增删。)
上代码(简单点我继续来改手机类把,装饰它):
public interface Phone {
public void call();
}
public class VideoPhone implements Phone {
@Override
public void call(){
System.out.println("打电话");
}
}
//装饰类
public class DecoratorPhone implements Phone{
private Phone phone;
public DecoratorPhone(Phone phones){
super();
phone = phones;
}
@Override
public void call(){
//原版
phone.call();
//装饰代码
System.out.println("装饰了");
}
}
//测试
public class AdapterTest {
public static void main(String[] args){
Phone phone = new VideoPhone();
Phone DecoratorPhone = new DecoratorPhone(phone);
DecoratorPhone.call();
}
}
//输出:
//打电话
//装饰了
需要装饰的类,被装饰着持有,调用原有方法,并且执行装饰后的方法。
3代理模式(Proxy)
用过spring的都知道,代理模式是spring框架的核心之一,框架中使用了大量的代理模式。
spring中的AOP(面向切面编程)和IOC(控制翻转)。
AOP的核心就是代理模式中的动态代理。
至于想了解spring是怎么动态代理的有个核心类ProxyCreatorSupport,自己去看吧,这里只是将设计模式。
上代理模式的代码,和装饰模式很像,最大的区别在ProxyPhone类里面,打了注解仔细看:
public interface Phone {
public void call();
}
public class VideoPhone implements Phone {
@Override
public void call(){
System.out.println("打电话");
}
}
//代理类
public class ProxyPhone implements Phone{
private Phone phone;
//代理模式和装饰模式最大区别 构造方法中不需要传入代理的类,在编译的时候就确定对象
public ProxyPhone(){
super();
phone = new VideoPhone();
}
@Override
public void call(){
//原版
phone.call();
//装饰代码
System.out.println("装饰了");
}
}
//测试
public class ProxyTest {
public static void main(String[] args){
Phone proxyPhone = new ProxyPhone();
proxyPhone.call();
}
}
//输出:
//打电话
//代理后加了方法
4外观模式
外观(Facade)模式又叫作门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。
外观模式最典型的应用就是java的三层开发模式,来个简单的外观模式例子,再来理解三层开发吧:
public class Dao {
public void getData(){
System.out.println("Dao拿数据");
}
}
public class Service {
public void getData(){
System.out.println("逻辑层处理数据");
}
}
public class Controller {
public void getData(){
System.out.println("控制层处理数据");
}
}
//外观页面
public class FacadeUI {
private Controller controller;
private Service service;
private Dao dao;
public FacadeUI(){
controller = new Controller();
service = new Service();
dao = new Dao();
}
public void getData(){
System.out.println("页面拿数据");
dao.getData();
service.getData();
controller.getData();
}
}
//测试
public class FacadeTest {
public static void main(String[] args){
FacadeUI ui = new FacadeUI();
ui.getData();
}
}
//输出
//页面拿数据
//Dao拿数据
//逻辑层处理数据
//控制层处理数据
在测试类中其实只是调用了外观页面的getData()方法,表面上看来就一个方法,但是实际后台为了拿数据分别调用了三层逻辑代码。这就是外观模式的最经典应用。
在实际开发中,三层是分开了,但是现在的mvc框架自动封装了’FacadeUI’,程序员们只需要开发三层中的逻辑。
现在的开发大多使用spring和mybatis,封装后外观模式提现的不是那么明显,jdbc取数据也是直接使用框架调用接口。
5桥接模式(Bridge)
桥接模式是将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(interface)模式。–百度百科
桥接的用意是:将抽象化与实现化解耦,使得二者可以独立变化。
桥接模式最大的使用示例就是JDBC连接数据库,JDBC进行连接数据库的时候,在各个数据库之间进行切换,基本不需要动太多的代码,甚至丝毫不用动,原因就是JDBC提供统一接口,每个数据库提供各自的实现,用一个叫做数据库驱动的程序来桥接就行了。
写一个非常简单的例子来模拟桥接模式:
/**
* jdbc模拟
*/
public interface JDBCDemo {
public String getData();
}
//Mysql实现jdbc
public class MysqlData implements JDBCDemo{
public String getData(){
System.out.println("Mysql查询数据");
return "mysql数据";
}
}
//Oracle实现jdbc
public class OracleData implements JDBCDemo {
public String getData(){
System.out.println("Oracle查询数据");
return "Oracle数据";
}
}
/**
* 桥接实体 持有JDBC接口(桥)
*/
public class BridgeExample {
public JDBCDemo jdbc;
//这里简单的例子没有使用框架的注入
public void setJdbc(JDBCDemo jdbc){
this.jdbc = jdbc;
}
public JDBCDemo getJdbc(){
return jdbc;
}
public String getData(){
return jdbc.getData();
}
}
/**
* 桥接实体
*/
public class MyBridge extends BridgeExample{
public String getData(){
return getJdbc().getData();
}
}
public class Test {
public static void main(String[] args) {
BridgeExample bridge = new MyBridge();
/*调用第一个对象*/
JDBCDemo jdbc = new MysqlData();
bridge.setJdbc(jdbc);
String data = bridge.getData();
System.out.println(data);
BridgeExample bridge2 = new MyBridge();
/*调用第二个对象*/
JDBCDemo jdbc2 = new OracleData();
bridge2.setJdbc(jdbc2);
String data2 = bridge2.getData();
System.out.println(data2);
}
}
//输出:Mysql查询数据
//mysql数据
//Oracle查询数据
//Oracle数据
从例子上可以看出,调用两个数据库其实使用的方法都是一样的,真实数据库实现桥接其实就是每个数据库有不同的driver接口,oracle数据库的是oracle.jdbc.driver.OracleDriver,mysql数据库的是com.mysql.cj.jdbc.Driver。
在JAVA里面使用的增删改查其实都是一样的接口,只是调用数据库的驱动不一样。
6组合模式(Composite)
组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。二叉树就是一个组合模式典型的例子。
写个简单的组合模式吧:
import java.util.ArrayList;
import java.util.List;
public class TreeNode {
private String name;
private TreeNode parent;
private List<TreeNode> children = new ArrayList<TreeNode>();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public TreeNode(String name){
this.name = name;
}
public TreeNode getParent() {
return parent;
}
public void setParent(TreeNode parent) {
this.parent = parent;
}
//添加子节点
public void add(TreeNode childrenNode){
children.add(childrenNode);
}
//删除子节点
public void remove(TreeNode childrenNode){
children.remove(childrenNode);
}
//拿到子节点
public List<TreeNode> getChildren( ){
return children;
}
//测试打印 所以复写toString
@Override
public String toString() {
return "TreeNode{" +
"name='" + name + '\'' +
", parent=" + parent +
", children=" + children +
'}';
}
}
public class NodeTest {
public static void main(String[] args) {
TreeNode parent = new TreeNode("parent");
TreeNode c1 = new TreeNode("c1");
TreeNode c2 = new TreeNode("c2");
parent.add(c1);
parent.add(c2);
//打印添加
System.out.println(parent.getChildren().toString());
parent.remove(c2);
//删除后再打印
System.out.println(parent.getChildren().toString());
}
//输出:[TreeNode{name='c1', parent=null, children=[]}, TreeNode{name='c2', parent=null, children=[]}]
//[TreeNode{name='c1', parent=null, children=[]}]
简单的树形结构就是这样 树节点知道自己的parent和children,一般遍历树的时候没有parent就是树的初始节点,没有children的就是树结构的末尾节点(看HashMap的源码可以充分提现这样的链表结构树)。
7享元模式(Flyweight)
享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。
当系统中对象多的时候可以减少内存的开销,通常与工厂模式一起使用。