结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
适配器模式(Adapter)
适配器模式将某个类的接口转换成客户端期望的另一个接口表示,
目的是消除由于接口不匹配所造成的类的兼容性问题。
主要分为三类:类的适配器模式、对象的适配器模式、接口的适配器模式。
类的适配器模式:
核心思想就是:有一个Source类,拥有一个方法,待适配,目标接口是Targetable,通过Adapter类,将Source的功能扩展到Targetable里
public class Source {
public void method1() {
System.out.println("this is original method!");
}
}
public interface Targetable {
/* 与原类中的方法相同 */
public void method1();
/* 新的方法 */
public void method2();
}
//类Source和接口Targetable因为不兼容,导致不能在一起工作
//适配器Adapter则可以在不改变源代码的基础上解决这个问题
//这样Targetable接口的实现类Adapter的对象即使Targetable类型,也能访问到Source中的方法
public class Adapter extends Source implements Targetable {
public void method2() {
System.out.println("this is the targetable method!");
}
}
//测试类 这样Targetable接口的实现类就具有了Source类的功能。
public class AdapterTest {
public static void main(String[] args) {
Targetable target = new Adapter();
target.method1();
target.method2();
}
}
对象的适配器模式
基本思路和类的适配器模式相同,只是将Adapter类作修改,这次不继承Source类,而是持有Source类的实例,以达到解决兼容性的问题
//只需要修改Adapter类的源码即可:
public class Wrapper implements Targetable {
private Source source;
public Wrapper(Source source){
this.source = source;
}
public void method2() {
System.out.println("this is the targetable method!");
}
public void method1() {
source.method1();
}
}
//测试类 输出与第一种情况一样,只是使用的适配方法不同而已。
public class AdapterTest {
public static void main(String[] args) {
Source source = new Source();
Targetable target = new Wrapper(source);
target.method1();
target.method2();
}
}
接口的适配器模式
接口的适配器是这样的:有时我们写的一个接口中有多个抽象方法,当我们写该接口的实现类时,必须实现该接口的所有方法,这明显有时比较浪费,因为并不是所有的方法都是我们需要的,有时只需要某一些,此处为了解决这个问题,我们引入了接口的适配器模式,借助于一个抽象类,该抽象类实现了该接口,实现了所有的方法,而我们不和原始的接口打交道,只和该抽象类取得联系,所以我们写一个类,继承该抽象类,重写我们需要的方法就行。
比如在写GUI程序时的监听器接口的适配器类:XxxxAdapter
public interface Sourceable {
public void method1();
public void method2();
}
//抽象类
public abstract class Wrapper implements Sourceable{
public void method1(){}
public void method2(){}
}
之后在我们写的子类中需要什么方法去重写什么方法就可以了,就不需要把接口中的所有方法都实现了
三种情况适配器模式的总结:
- 类的适配器模式:当希望将一个类转换成满足另一个新接口的类时,可以使用类的适配器模式,创建一个新类,继承原有的类,实现新的接口即可。
- 对象的适配器模式:当希望将一个对象转换成满足另一个新接口的对象时,可以创建一个Wrapper类,持有原类的一个实例,在Wrapper类的方法中,调用实例的方法就行。
- 接口的适配器模式:当不希望实现一个接口中所有的方法时,可以创建一个抽象类Wrapper,实现所有方法,我们写别的类的时候,继承抽象类即可。
装饰模式(Decorator)
顾名思义,装饰模式就是给一个对象增加一些新的功能,而且是【动态】的,要求装饰对象和被装饰对象实现同一个接口,装饰对象持有被装饰对象的实例这里的动态指的是用户可以根据自己的需求把之前定好的功能任意组合。 JDK中的IO流部分就是典型的使用了装饰模式,回忆一下BufferedReader对象的是如何创建的
//功能接口
public interface Action {
public void go();
}
//被装饰的类 就是需要我们装饰的目标
public class Person implements Action{
public void go() {
System.out.println("我在走路");
}
}
//抽象的装饰类
public abstract class Decorator implements Action{
private Action action;
public Decorator(Action action) {
this.action = action;
}
public void go() {
this.action.go();
}
}
//具体的装饰类 可以添加一个听音乐的功能
public class ListenDecorator extends Decorator{
public ListenDecorator(Action action) {
super(action);
}
public void go() {
listen();//可以在go方法【前】添加一个听音乐的功能
super.go();
}
public void listen(){
System.out.println("我在听音乐");
}
}
//具体的装饰类 可以添加一个休息的功能
public class RelaxDecorator extends Decorator{
public RelaxDecorator(Action action) {
super(action);
}
public void go() {
super.go();
relax();//可以在go方法【后】添加一个休息的功能
}
public void relax(){
System.out.println("我在休息");
}
}
//测试类
public class Test {
/*用户可以根据需求 任意给go方法添加听音乐或者休息的功能*/
//Action a = new Person();
//Action a = new ListenDecorator(new Person());
//Action a = new RelaxDecorator(new Person());
//Action a = new RelaxDecorator(new ListenDecorator(new Person()));
Action a = new ListenDecorator(new RelaxDecorator(new Person()));
a.go();
}
装饰器模式的应用场景:
1、需要扩展一个类的功能。
2、动态的为一个对象增加功能,而且还能动态撤销。
缺点:产生过多相似的对象,不易排错!
代理模式(Proxy)
代理模式就是多一个代理类出来,替原对象进行一些操作,比如我们在租房子的时候回去找中介,为什么呢?
因为你对该地区房屋的信息掌握的不够全面,希望找一个更熟悉的人去帮你做,此处的代理就是这个意思。再如我们有的时候打官司,我们需要请律师,因为律师在法律方面有专长,可以替我们进行操作,表达我们的想法。
//公共接口
public interface Sourceable {
public void method();
}
//目标类/被代理类
public class Source implements Sourceable {
public void method() {
System.out.println("the original method!");
}
}
//代理类
public class Proxy implements Sourceable {
private Source source;
public Proxy(Source source){
this.source = source;
}
public void method() {
before();
source.method();
atfer();
}
private void after() {
System.out.println("after proxy!");
}
private void before() {
System.out.println("before proxy!");
}
}
//测试类
public class ProxyTest {
public static void main(String[] args) {
Source target = new Source();
Sourceable proxy = new Proxy(target);
proxy.method();
}
}
代理模式的应用场景:
如果已有的方法在使用的时候需要对原有的方法进行改进,此时有两种办法:
1、修改原有的方法来适应。这样违反了“对扩展开放,对修改关闭”的原则。
2、就是采用一个代理类调用原有的方法,且对产生的结果进行控制。这种方法就是代理模式。
使用代理模式,可以将功能划分的更加清晰,有助于后期维护!
注意: 装饰模式和代理模式在很多情况下,大部分代码都是类似的,但是这俩种设计的意图是不一样的,装饰模式是增强被包装对象的功能,代理模式是控制被代理对象的行为
例如一块代码,如果被描述为使用了装饰模式,那么我们就知道设计的意图是增加被包装对象的功能,如果被描述为使用了代理模式,那么我们就知道设计的意图是控制被代理对象的行为,虽然这俩种情况下他们的代码结构基本相同.
装饰器模式:能动态的新增或组合对象的行为。
代理模式 :为目标对象提供一种代理以便控制对这个对象的访问.
装饰模式是“新增行为”,而代理模式是“控制访问”。
1.装饰模式:对被装饰的对象增加额外的行为
如:杯子生产线,杯子必须可以装水,在生产线上可以给杯子涂颜色,加杯盖,但要保证杯子可以装水。
2.代理模式:对被代理的对象提供访问控制。
如:客户网上商城订购商品,网上商城是厂家的代理,网上商城可以帮客户完成订购商品的任务,但是商城可以对商品进行控制,不交钱不给商品,人不在不给商品,也可以赠送你额外的礼品,代金券。
创建时期,代理类可分为两种:
静态代理类:
由程序员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。动态代理类:在程序运行时,运用反射机制动态创建而成。
与静态代理类对照的是动态代理类,动态代理类的字节码在程序运行时由Java反射机制动态生成,
无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,
因为Java 反射机制可以生成任意类型的动态代理类。java.lang.reflect 包下面的Proxy类和InvocationHandler 接口提供了生成动态代理类的能力。CGLib代理(第三方类库)
JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。
CGLib 采用了非常底层的字节码技术,其原理是通过字节码技术为目标对象创建一个子类对象,并在子类对象中
拦截所有父类方法的调用,然后在方法调用前后调用后都可以加入自己想要执行的代码。
需要这种方法只是需要俩个第三方jar包: cglib-3.2.1.jar和asm-5.0.4.jar
同时很多框架已经把这些jar包整合到一起了,比如spring框架的spring-core-3.2.4.RELEASE.jar,
这一个jar包就包括上述俩个jar包的大多数功能
静态代理: staticProxy
//公共接口
public interface HelloService {
void sayHello();
}
//委托类
public class HelloServiceImpl implements HelloService{
public void sayHello() {
System.out.println("hello world");
}
}
//代理类
public class HelloServiceProxy implements HelloService{
private HelloService target;
public HelloServiceProxy(HelloService target) {
this.target = target;
}
public void sayHello() {
System.out.println("log:sayHello马上要执行了...");
target.sayHello();
}
}
//测试类
public class Test {
public static void main(String[] args) {
//目标对象
HelloService target = new HelloServiceImpl();
//代理对象
HelloService proxy = new HelloServiceProxy(target);
proxy.sayHello();
}
}
JDK的动态代理: dynamicProxy
//Student类
public class Student {
private long id;
private String name;
private int age;
get/set
}
//日志类
public class StudentLogger {
public void log(String msg){
System.out.println("log: "+msg);
}
}
//Service接口 处理学生的相关业务
public interface IStudentService {
void save(Student s);
void delete(long id);
Student find(long id);
}
//接口的一个简单实现
public class StudentServiceImpl implements IStudentService {
public void delete(long id) {
System.out.println("student is deleted...");
}
public Student find(long id) {
System.out.println("student is found...");
return null;
}
public void save(Student s) {
System.out.println("student is saved...");
}
}
//InvocationHandler接口的实现类
//JDK动态代理中必须用到的接口实现
public class MyHandler implements InvocationHandler{
private Object target;
private StudentLogger logger = new StudentLogger();
public MyHandler(Object target, StudentLogger logger) {
this.target = target;
this.logger = logger;
}
public MyHandler(Object target) {
this.target = target;
}
//参数1 proxy 将来给目标对象所动态产生的代理对象
//参数2 method 将来你所调用的目标对象中的方法的镜像
//参数3 args 将来你所调用方法的时候所传的参数
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String msg = method.getName()+"方法被调用了...";
logger.log(msg);
Object o = method.invoke(target, args);
return o;
}
}
//测试类
public class DProxyTest {
public static void main(String[] args) {
IStudentService target = new StudentServiceImpl();
ClassLoader loader = target.getClass().getClassLoader();
Class<?>[] interfaces = target.getClass().getInterfaces();
InvocationHandler h = new MyHandler(target);
//参数1 loader 目标对象的类加载器
//参数2 interfaces 目标对象所实现的接口
//参数3 h InvocationHandler接口的实现类对象
IStudentService proxy = (IStudentService)Proxy.newProxyInstance(loader, interfaces, h);
proxy.delete(1);
proxy.save(null);
proxy.find(1);
System.out.println(proxy.toString());
System.out.println(proxy.getClass());
System.out.println(target.getClass());
}
}
第三方jar包提供的动态代理(cglib) CglibProxy
//目标的对象 没有实现接口
public class BookService {
public void addBook() {
System.out.println("添加书籍成功");
}
}
//产生代理对象的工厂类
public class MyCglibProxyFactory implements MethodInterceptor {
public Object getInstance(Class<?> c) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(c);
enhancer.setCallback(this);
return enhancer.create();
}
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("开始执行方法");
//这句代码最终会执行到我们目标对象中的方法
proxy.invokeSuper(obj, args);
System.out.println("方法执行结束");
return null;
}
}
//测试类
public class TestCglibProxy {
public static void main(String[] args) {
MyCglibProxyFactory cglib=new MyCglibProxyFactory();
BookService bookCglib=
(BookService)cglib.getInstance(new BookService().getClass());
bookCglib.addBook();
System.out.println(bookCglib.getClass());
}
}
外观模式(Facade)
外观模式也可以叫做门面模式
为子系统或者模块中的一组接口提供一个一致的访问方式,此模式定义了一个高层接口,这个接口使得各个子系统/模块中的功能更加容易使用.
实际应用中,我们在对付一些老旧的代码或者即便不是老旧code,但涉及多个子系统时,除了重写全部代码,我们还可能采用这样一种策略:重新进行类的设计,将原来分散在源码中的类/结构及方法重新组合,形成新的、统一的接口,供上层应用使用,同时也隐藏了子系统或者子模块中功能实现的复杂性
//模块A中的类
public class ServiceA {
public void start(){
System.out.println("模块A中的start方法");
}
}
//模块B中的类
public class ServiceB {
public void run(){
System.out.println("模块B中的run方法");
}
}
//模块C中的类
public class ServiceC {
public void end(){
System.out.println("模块C中的end方法");
}
}
//外观类/门面类
public class Facade {
private ServiceA a;
private ServiceB b;
private ServiceC c;
public Facade() {
a = new ServiceA();
b = new ServiceB();
c = new ServiceC();
}
public void start(){
a.start();
}
public void run(){
b.run();
}
public void end(){
c.end();
}
public void service(){
a.start();
b.run();
c.end();
}
}
//测试类
public class Test {
public static void main(String[] args) {
Facade f = new Facade();
f.start();
f.run();
f.end();
f.service();
}
}
Facade是我们的外观类/门面类,用户可以通过这个类使用到系统中不同模块中的不同方法,同时也对用户隐藏了系统中对这些功能的实现细节。给用户提供了一个统一的访问方式。
桥接模式(Bridge)
桥接模式(也叫桥梁模式)就是将抽象部分和实现部分分离,使它们都可以独立的变化。桥接的用意是:将抽象化与实现化解耦,使得二者可以独立变化,像我们常用的JDBC桥DriverManager一样,JDBC进行连接数据库的时候,在各个数据库之间进行切换,基本不需要动太多的代码,甚至丝毫不用动,原因就是JDBC提供统一接口,每个数据库提供各自的实现,用一个叫做数据库驱动的程序来桥接就行了。
//公共的驱动接口
public interface Driver {
public void getConnection();
}
//第一个实现类 mysql驱动类
public class MysqlDriver implements Driver{
public void getConnection() {
System.out.println("mysql 数据库连接");
}
}
//第二个实现类 oracle驱动类
public class OracleDriver implements Driver {
public void getConnection() {
System.out.println("oracle数据库连接");
}
}
//抽象的管理器 Bridge
public abstract class Manager {
private Driver driver;
public void getConnection(){
driver.getConnection();
}
public void setDriver(Driver driver) {
this.driver = driver;
}
}
//具体的驱动管理器 Bridge
public class DriverManager extends Manager {
public DriverManager(Driver driver){
setDriver(driver);
}
public void getConnection() {
super.getConnection();
}
}
//测试类 注意我们的抽象和具体实现是分开的,无论他们如何变化都不会影响到我们bridge中的功能执行
//JDBC中,我们使用的就是一系列javaAPI提供的接口,而且数据公司商则给我们提供接口的实现
public class Test {
public static void main(String[] args) {
DriverManager manager = new DriverManager(new MysqlDriver());
manager.getConnection();
manager = new DriverManager(new OracleDriver());
manager.getConnection();
}
}
组合模式(Composite)
组合模式有时又叫部分-整体模式,在处理类似树形结构的问题时比较方便
//节点类
public class TreeNode {
private String name;
private TreeNode parent;
private Vector<TreeNode> children = new Vector<TreeNode>();
public TreeNode(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public TreeNode getParent() {
return parent;
}
public void setParent(TreeNode parent) {
this.parent = parent;
}
//添加孩子节点
public void add(TreeNode node){
children.add(node);
}
//删除孩子节点
public void remove(TreeNode node){
children.remove(node);
}
//取得孩子节点
public Enumeration<TreeNode> getChildren(){
return children.elements();
}
}
//表示一个树状结构
public class Tree {
TreeNode root = null;
public Tree(String name) {
root = new TreeNode(name);
}
}
//测试类
public class Test{
public static void main(String[] args) {
Tree tree = new Tree("A");
TreeNode nodeB = new TreeNode("B");
TreeNode nodeC = new TreeNode("C");
nodeB.add(nodeC);
tree.root.add(nodeB);
System.out.println("build the tree finished!");
}
}
享元模式(Flyweight)
享元模式的主要目的是实现对象的共享,即共享池,当系统中对象多的时候可以减少内存的开销,通常与工厂模式一起使用。
//数据库连接池
public class ConnectionPool {
private Vector<Connection> pool;
/*公有属性*/
private String url = "jdbc:mysql://localhost:3306/test";
private String username = "root";
private String password = "root";
private String driverClassName = "com.mysql.jdbc.Driver";
private int poolSize = 100;
//这里对instance可以使用一个单例模式
private static ConnectionPool instance = null;
Connection conn = null;
/*构造方法,做一些初始化工作*/
private ConnectionPool() {
pool = new Vector<Connection>(poolSize);
for (int i = 0; i < poolSize; i++) {
try {
Class.forName(driverClassName);
conn = DriverManager.getConnection(url, username, password);
pool.add(conn);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/* 把连接对象返回到连接池 */
public synchronized void release() {
pool.add(conn);
}
/* 返回连接池中的一个数据库连接 */
public synchronized Connection getConnection() {
if (pool.size() > 0) {
Connection conn = pool.get(0);
pool.remove(conn);
return conn;
} else {
return null;
}
}