设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案,是一套被反复使用、多数人知晓、及经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保障代码的可靠性,可维护性。
- 针对接口编程的意思就是说,应当使用Java接口和抽象Java类进行变量的类型声明、参量的类型声明、方法的返回类型声明,以及数据类型的转换等。
- 不要针对实现编程的意思就是说,不应当使用具体Java类进行变量的类型声明、参量的类型声明、方法的返回类型声明,以及数据类型的转换等。
通俗一点来讲就是接口应该尽量细化,一个接口对应一个功能模块的规范,同时让里面的方法尽可能少,使接口轻便灵活
通俗一点来讲就是一个类对自己需要耦合或者调用的类知道的最少,你(被耦合或者调用的类)的内部是如何复杂和我没有关系,我就知道你提供的public方法,其他我不关心。
工厂模式:创建者和调用者分离
4.1 简单工厂模式
定义:定义一个生成对象的工厂类,客户端通过工厂类的静态方法来获取对象。
说明:我们定义一个生产汽车的工厂,客户端通过调用工厂类的静态方法并传入所需汽车的名字来获取对象。
缺点:如果我们需要增加其他的车型,那么便需要去工厂类去改变其原则代码,显然这违背了开闭原则 扩展性低。
4.2 工厂方法模式
定义:定义一个生产对象的接口类,由子类实现接口来决定要实例化的类。工厂方法模式将生产的对象推迟到子类去实现。
工厂方法模式与简单工厂模式的比较
- 简单工厂模式是生产单一的产品
- 工厂方法模式是声场一个系列的产品族
接口工厂类
具体的工厂类
测试类
说明:根据不同的车型去实现不同的工厂类,通过其对应的工厂类来获取相应的车型。
优点:相对简单工厂模式,它支持横向扩展,无需改变源代码
缺点:相对简单工厂模式,它代码冗余读高,难以维护。
它是创建工厂的工厂,可以称为超级工厂
定义:定义了一个接口用于创建相关或由依赖关系的对象族,而无需明确指定具体类。比如说小米产品系列,其旗下产品有小米手机,小米充电器,小米路由器等等。
抽象工厂模式与工厂方法模式很类似
定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。用户只需给出指定类型的复杂对象和内容,指挥者指挥建造者按照指定的顺序创建复杂的对象。
建造者模式与抽象工厂的比较
- 个人理解建造者模式如下:建造者模式与工厂方法模式有点类似,只不过工厂方法的工厂类是直接生产出完成的产品,而建造者模式下该工厂(工人)则是生产出产品的一个个组件,然后需要一个指挥者指挥工人按照指定的顺序去拼接出一个产品。
建造者模式与抽象工厂的比较
- 建造者模式返回一个组装完整的产品,而抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构构成了产品族。
- 在抽象工厂模式中,客户端实例化工厂类,然后调用工厂方法来获取所需的产品对象,而在建造者模式中,客户端可以通过指挥者类来指导如何生产对象,也可以调用建造者的相关方法来指导生产对象,它侧重一步步构建一个复杂的对象返回一个完整的产品对象。
如果将抽象工厂模式看做汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一个完整的汽车
产品类-一个产品的所有属性
抽象建造者类-定义建造者需要做的组件
具体建造者类-创建出产品的所有组件
指挥者-指挥工人生成房子
说明:具体的工厂(工人)去生产产品的一个个零件 ,由指挥者去指挥工人按照指定的顺序去拼接。
优点
缺点
定义:以一个对象为模板,创建出一个一模一样的深拷贝对象
实现方式:
- 实现Cloneable接口。
- 重写clone方法实现深拷贝
草帽小子–面试常问设计模式——单例模式
定义:适配器模式是将某一个接口转换成客户端期待的另一个接口,目的是消除由于接口不匹配所造成的的类的不兼容性问题。
主要有三类实现方式:类的适配器模式、对象的适配器模式、接口的适配器模式。
使用方式
客户端期待的接口XXX,定义一个适配器类去实现接口XXX,并在适配器类中可通过继承被适配类或者创建被适配类对象的方式获取目标API方法,然后在适配器类的方法中调用目标函数,而客户端直接调用适配器的方法便可实现转换
7.1 类适配器模式
通过多重继承目标接口和被适配者类方式实现适配
举例:将USB接口转换为VGA接口
USBImpl的代码:
public class USBImpl implements USB{
@Override
public void showPPT() {
// TODO Auto-generated method stub
System.out.println("PPT内容演示");
}
}
AdatperUSB2VGA 首先继承USBImpl获取可连接USB的功能,其次,实现VGA接口,表示该类的类型为VGA。
public class AdapterUSB2VGA extends USBImpl implements VGA {
@Override
public void projection() {
super.showPPT();
}
}
Projector将USB映射为VGA,只有VGA接口才可以连接上投影仪进行投影
public class Projector<T> {
public void projection(T t) {
if (t instanceof VGA) {
System.out.println("开始投影");
VGA v = new VGAImpl();
v = (VGA) t;
v.projection();
} else {
System.out.println("接口不匹配,无法投影");
}
}
}
test代码
@Test
public void test2(){
//通过适配器创建一个VGA对象,这个适配器实际是使用的是USB的showPPT()方法
VGA a=new AdapterUSB2VGA();
//进行投影
Projector p1=new Projector();
p1.projection(a);
}
7.2 对象适配器模式
对象适配器和类适配器使用了不同的方法实现适配,对象适配器使用组合,类适配器使用继承。
举例(将USB接口转为VGA接口),类图如下:
实现VGA接口是为了满足客户端的需求,客户端通过VGA类型对象调用适配器方法。
创建USB对象是为了在适配器中使用USB的功能。
public class AdapterUSB2VGA implements VGA {
USB u = new USBImpl();
@Override
public void projection() {
u.showPPT();
}
}
实现VGA接口,表示适配器类是VGA类型的,适配器方法中直接使用USB对象。
7.3 接口适配器模式
当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求,它适用于一个接口不想使用其所有的方法的情况。
举例(将USB接口转为VGA接口,VGA中的b()和c()不会被实现),类图如下:
AdapterUSB2VGA抽象类
public abstract class AdapterUSB2VGA implements VGA {
USB u = new USBImpl();
@Override
public void projection() {
u.showPPT();
}
@Override
public void b() {
};
@Override
public void c() {
};
}
AdapterUSB2VGA实现,不用去实现b()和c()方法。
public class AdapterUSB2VGAImpl extends AdapterUSB2VGA {
public void projection() {
super.projection();
}
}
总结
使用选择:
根据合成复用原则,组合大于继承。因此,类的适配器模式应该少用。
定义: 将抽象部分与它的实现部分分离,使它们都可以独立地变化。
桥接模式与适配器模式的比较 共同点:桥接和适配器都是让两个东西配合工作。 不同点:
- 适配器:改变已有的两个接口,让它们相容
- 桥接模式:分离抽象化和实现,目的是分离,适合维度变化
8.1 案例
看下图手机与手机软件的类图
增加一款新的手机软件,需要在所有手机品牌类下添加对应的手机软件类,当手机软件种类较多时,将导致类的个数急剧膨胀,难以维护。
手机和手机中的软件是什么关系?
手机中的软件从本质上来说并不是一种手机,手机软件运行在手机中,是一种包含与被包含关系,而不是一种父与子或者说一般与特殊的关系,通过继承手机类实现手机软件类的设计是违反一般规律的。
换一种解决思路
从类图上看起来更像是手机软件类图,涉及到手机本身相关的功能,比如说:wifi功能,放到哪个类中实现呢?放到OppoAppStore中实现显然是不合适的。
引起整个结构变化的元素有两个,一个是手机品牌,一个是手机软件,所以我们将这两个点抽出来,分别进行封装。
8.2 桥接模式结构和代码示例
实现:
public interface Software {
public void run();
}
public class AppStore implements Software {
@Override
public void run() {
System.out.println("run app store");
}
}
public class Camera implements Software {
@Override
public void run() {
System.out.println("run camera");
}
}
抽象:
public abstract class Phone {
protected Software software;
public void setSoftware(Software software) {
this.software = software;
}
public abstract void run();
}
public class Oppo extends Phone {
@Override
public void run() {
software.run();
}
}
public class Vivo extends Phone {
@Override
public void run() {
software.run();
}
}
对比最初的设计,将抽象部分(手机)与它的实现部分(手机软件类)分离,将实现部分抽象成单独的类,使它们都可以独立地变化。整个类图看起来像一座桥,所以称为桥接模式
继承是一种强耦合关系,子类的实现与它的父类有非常紧密的依赖关系,父类的任何变化 都会导致子类发生变化,因此继承或者说强耦合关系严重影响了类的灵活性,并最终限制了可复用性
从桥接模式的设计上我们可以看出聚合是一种比继承要弱的关联关系,手机类和软件类都可独立的进行变化,不会互相影响
9.3 适用场景
桥接模式通常适用于以下场景。
8.4 优缺点
优点:
缺点:
定义:动态的奖新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性。
星巴克咖啡订单项目(咖啡馆):
定义: 有时又叫作部分-整体模式,它是一种将对象组合成树状的层次结构的模式,用来表示“部分-整体”的关系,使用户对单个对象和组合对象具有一致的访问性。
意图: 将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
主要解决:它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。
何时使用:
1、您想表示对象的部分-整体层次结构(树形结构)。
2、您希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
组合模式的主要优点:
其主要缺点是:
10.1 模式结构和代码示例
抽象构件(Component)角色:它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成。
树叶构件(Leaf)角色:是组合中的叶节点对象,它没有子节点,用于实现抽象构件角色中 声明的公共接口。
树枝构件(Composite)角色:是组合中的分支节点对象,它有子节点。它实现了抽象构件角色中声明的接口,它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法
public interface Component {
public void add(Component c);
public void remove(Component c);
public Component getChild(int i);
public void operation();
}
2 树枝
public class Composite implements Component {
private ArrayList<Component> children = new ArrayList<Component>();
public void add(Component c) {
children.add(c);
}
public void remove(Component c) {
children.remove(c);
}
public Component getChild(int i) {
return children.get(i);
}
public void operation() {
for (Object obj : children) {
((Component) obj).operation();
}
}
}
3 叶子
public class Leaf implements Component{
private String name;
public Leaf(String name) {
this.name = name;
}
@Override
public void add(Component c) {}
@Override
public void remove(Component c) {}
@Override
public Component getChild(int i) {
// TODO Auto-generated method stub
return null;
}
@Override
public void operation() {
// TODO Auto-generated method stub
System.out.println("树叶"+name+":被访问!");
}
}
11.1 静态代理
静态代理在程序运行之处就已经写死,无法修改代理类
静态代理角色分析
上代码
Rent.java 即抽象角色:要出租房子
//抽象角色:租房
public interface Rent {
public void rent();
}
Host.java 即真实角色-房东要出租房子
//真实角色: 房东,房东要出租房子
public class Host implements Rent{
public void rent() {
System.out.println("房屋出租");
}
}
Proxy.java 即代理角色-中介帮助房东租出房子
//代理角色:中介
public class Proxy implements Rent {
private Host host;
public Proxy() { }
public Proxy(Host host) {
this.host = host;
}
//租房
public void rent(){
seeHouse();
host.rent();
fare();
}
//看房
public void seeHouse(){
System.out.println("带房客看房");
}
//收中介费
public void fare(){
System.out.println("收中介费");
}
}
Client.java 即客户-客户向中介租房子
//客户类,一般客户都会去找代理!
public class Client {
public static void main(String[] args) {
//房东要租房
Host host = new Host();
//中介帮助房东
Proxy proxy = new Proxy(host);
//你去找中介!
proxy.rent();
}
}
静态代理的好处
静态代理的缺点 :
11.2 动态代理
没有向静态代理一样一开始就直接写死代理的对象,而是根据具体的真实角色去代理。
静态代理和动态代理的比较
动态代理的方式
基于接口的JDK动态代理需要了解两个类
核心 : InvocationHandler 和 Proxy , 打开JDK帮助文档看看
【InvocationHandler:调用处理程序】
Object invoke(Object proxy, 方法 method, Object[] args);
//参数
//proxy - 调用该方法的代理实例
//method -所述方法对应于调用代理实例上的接口方法的实例。方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。
//args -包含的方法调用传递代理实例的参数值的对象的阵列,或null如果接口方法没有参数。原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer或java.lang.Boolean 。
//生成代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
rent.getClass().getInterfaces(),this);
}
上代码
抽象角色和真实角色和之前的一样!
Rent.java 即抽象角色
//抽象角色:租房
public interface Rent {
public void rent();
}
Host.java 即真实角色
//真实角色: 房东,房东要出租房子
public class Host implements Rent{
public void rent() {
System.out.println("房屋出租");
}
}
ProxyInvocationHandler.java 即代理角色
public class ProxyInvocationHandler implements InvocationHandler {
private Object target;
public void setTarget(Object target) {
this.target = target;
}
//生成代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(),this);
}
// proxy : 代理类
// method : 代理类的调用处理程序的方法对象.
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method.getName());
Object result = method.invoke(target, args);
return result;
}
public void log(String methodName){
System.out.println("执行了"+methodName+"方法");
}
}
Client.java
public class Test {
public static void main(String[] args) {
//真实对象
UserServiceImpl userService = new UserServiceImpl();
//代理对象的调用处理程序
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setTarget(userService); //设置要代理的对象
UserService proxy = (UserService)pih.getProxy(); //动态生成代理类!
proxy.delete();
}
}
动态代理的好处