设计模式(Design pattern),是一套被反复使用、经过分类编目的、代码设计经验的总结,使用设计模式是为了可重用代码、保证代码可靠性、程序的重用性。
1995 年,GoF(Gang of Four,四人组)合作出版了《设计模式:可复用面向对象软件的基础》一书,共收录了 23 种设计模式。
总体来说设计模式分为三大类:
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。-->创建对象
结构型模式,共七种:[适配器模式]、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。-->对功能进行增强
行为型模式,共十一种:策略模式、模板方法模式、[观察者模式]、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、[中介者模式]、解释器模式。
类一加载到内存,先加载静态的,使用下面的代码赶紧new一个对象来使用
单例模式:
目的:保证一个类只产生一个对象
饿汉式:迫不及待的使用对象
public class Singleton {
//在自己内部定义自己一个实例
//注意这是private 只供内部调用
private static Singleton instance = new Singleton();
//如上面所述,将构造函数设置为私有
private Singleton(){
}
//静态方法,提供了一个供外部访问得到对象的静态方法
public static Singleton getInstance() {
return instance;
}
}
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
Singleton instance = Singleton.getInstance();
System.out.println(instance);
}
}
}
1.先判断instance是不是空,如果是空,直接new
2.等下一次调用getInstance方法的时候再次判断是不是null,不是的话把第一次new出来的对象返回
3.这样线程不安全:
如果有一条线程进了if但是还没等new对象呢,另外一个线程也可以进if
再执行的话,由于两个线程都进了if,所以就会出现new两次的情况
4.怎么解决线程不安全问题:
在getInstance()方法上加一个synchronized关键字,变成同步的
public class Singleton {
/*
先不着急new对象,使用的时候调用getInstance方法的时候再new对象
*/
//和上面有什么不同?
private static Singleton instance = null;
//设置为私有的构造函数
private Singleton(){
}
//静态工厂方法
public static Singleton getInstance() {
//这个方法比上面有所改进
if (instance==null){
instance=new Singleton();
}
return instance;
}
}
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
Singleton instance = Singleton.getInstance();
System.out.println(instance);
}
}
}
单例模式可以保证系统中一个类只有一个对象实例。
实现单例模式的步骤:
1. 将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
2. 在该类内部产生一个唯一的实例化对象,并且将其封装为private static类型的成员变量。
3. 定义一个静态方法返回这个唯一对象。
多例模式,是一种常用的软件设计模式。通过多例模式可以保证系统中,应用该模式的类有固定数量的实例。多例类要自我创建并管理自己的实例,还要向外界提供获取本类实例的方法。
例如:
扑克牌程序,一个“扑克类”会创建固定的54个对象,不能多、也不能少。
麻将程序,一个“骰子类”会创建固定的2个对象,不能多、也不能少。
程序中需要用到“颜色的表示”,只能有三种颜色“红、绿、蓝”,一个“颜色类(Color)”应该只创建三个对象,来代 表这三个颜色。
多例模式的作用:使某个类,在程序运行期间,只能产生固定的几个对象,不能多、也不能少。
需求:只让程序产生3个对象,存储到集合,不能多也不能少
1.创建一个类, 将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
2.在类中定义该类被创建对象的总数量,就3个,不能多,也不能少
3.在类中定义存放类实例的list集合
4.在类中提供静态代码块,在静态代码块中创建类的对象,存储到集合中
5.提供获取类实例的静态方法
public class Multition {
// 定义该类被创建的总数量
private static final int maxCount = 3;
// 定义存放类实例的list集合
private static List list = new ArrayList();
// 构造方法私有化,不允许外界创建本类对象
private Multition() {
}
static {
// 创建本类的多个实例,并存放到list集合中
for (int i = 0; i < maxCount; i++) {
Multition multition = new Multition();
list.add(multition);
}
}
// 给外界提供一个获取类对象的方法
public static Multition getMultition(){
Random random = new Random();
// 生成一个随机数
int i = random.nextInt(maxCount);
// 从list集合中随机取出一个进行使用
return (Multition)list.get(i);
}
}
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
Multition multition = Multition.getMultition();
System.out.println(multition);
}
}
}
package com.itheima.demo09Multiton;
public class Student {
private String name;
private int age;
private String sex;
public Student() {
}
public Student(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
package com.itheima.demo09Multiton;
public class Demo01Multiton {
public static void main(String[] args) {
//创建Student对象
Student s = new Student();
s.setName("张三");
s.setAge(18);
//s.setSex("男");
s.setSex("abc");//性别是一个字符串,可以随意编写
System.out.println(s);
}
}
优化代码:解决用户随意填写性别问题
package com.itheima.demo10Multiton;
/*
多例设计模式:获取多个特定的对象
只产生两个Sex对象,一个代表男的性别,一个代表女的性别
实现步骤:
1.私有构造方法,不让用户直接创建对象
2.定义两个固定对象(公共的,静态的,最终的),一个代表男的性别,一个代表女的性别
3.重写toString方法,打印对象的属性值
*/
public class Sex {
private String s;
//1.私有构造方法,不让用户直接创建对象
private Sex(String s){
this.s = s;
}
//2.定义两个固定对象(公共的,静态的,最终的),一个代表男的性别,一个代表女的性别
public static final Sex MAN = new Sex("男");
public static final Sex WOMAN = new Sex("女");
//3.重写toString方法,打印对象的属性值
@Override
public String toString() {
return s;
}
}
package com.itheima.demo10Multiton;
public class Student {
private String name;
private int age;
private Sex sex;
public Student() {
}
public Student(String name, int age, Sex sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Sex getSex() {
return sex;
}
public void setSex(Sex sex) {
this.sex = sex;
}
}
package com.itheima.demo10Multiton;
public class Demo01Multiton {
public static void main(String[] args) {
//创建Student对象
Student s = new Student();
s.setName("张三");
s.setAge(18);
//s.setSex("abc");//性别的类型是Sex,不能随意给性别赋值,只能在Sex类中选择
//s.setSex(Sex.MAN);
s.setSex(Sex.WOMAN);
System.out.println(s);
}
}
多例模式可以保证系统中一个类有固定个数的实例, 在实现需求的基础上, 能够提高实例的复用性.
实现多例模式的步骤:
1. 创建一个类, 将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
2. 在类中定义该类被创建的总数量
3. 在类中定义存放类实例的list集合
4. 在类中提供静态代码块,在静态代码块中创建类的实例
5. 提供获取类实例的静态方法
简单工厂模式又称静态工厂方法模式。名上就可以看出这个模式一定很简单。它存在的目的很简单:定义一个用于创建对象的接口。
1.工厂用于造东西的(创建对象),但是不能随便造,不能创造不是一类的东西(对象)
2.所以,为了防止随便造,达成只能创建一类对象的目的,我们可以定义一个抽象类或者接口
3.让创建出来的类去继承抽象类或者实现接口
4.除了子类或者实现类,我们需要一个工厂去专门造对象
5.创建一个工厂类,定义一个方法,到时候调用这个方法的时候,让我创建啥,我就创建啥
6.弊端:
如果调用方法的时候传递了其他的车, createCar()方法就直接报NullPointerException()了
不想报NullPointerException(),就需要在此方法中加一个判断代码,这样会造成我们反复修改源代码的弊端
//抽象产品角色
public interface Car{
public void drive();
}
//具体产品角色
public class Benz implements Car{
public void drive() {
System.out.println("Driving Benz ");
}
}
public class Bmw implements Car{
public void drive() {
System.out.println("Driving Bmw ");
}
}
//工厂角色
public class CarFactory{
//工厂方法.注意 返回类型为抽象产品角色
public static Car createCar(String s) {
//判断逻辑,返回具体的产品角色
if(s.equalsIgnoreCase("Benz"))
return new Benz();
else if(s.equalsIgnoreCase("Bmw"))
return new Bmw();
return null;
}
public class Magnate{
public static void main(String[] args){
Car car = CarFactory.createCar("benz");
car.drive();
}
}
工厂方法模式去掉了简单工厂模式中工厂方法的静态属性,使得它可以被子类继承。这样在简单工厂模式里集中在工厂方法上的压力可以由工厂方法模式里不同的工厂子类来分担。
1.问题:
从上面的简单工厂代码来看,如果我们调用createCar,传递的不是Benz和Bmw,那么直接就返回空
再调用drive()方法的时候就报NullPinterException()了
2.工厂方法就是对简单工厂的延伸
3.怎么解决呢?
针对不同的对象,创建不同的工厂.不同的工厂创建不同的对象,我直接不判断了,也就避免了判断有可能失败的问题
4.好处:
a.不用像简单工厂那样,调用方法的时候传递参数而造成NullPinterException()了
b.我们如果传递别的汽车,我就不用像简单工厂那样再加一个判断了(这属于修改源代码,不好)
b.如果我们想创建别的牌子的车,我们直接再创建一个造此车的工厂,不用修改已经写好的代码了
public interface Car{
pubic void driver();
}
public class Benz implements Car{
public void drive() {
System.out.println("Driving Benz ");
}
}
//工厂角色
public interface Factory{
public Car createCar();
}
//具体工厂角色
public class BenzFactory implements Factory{
public Car createCar(){
return new Benz();
}
}
public class BmFactory implements Factory{
public Car createCar() {
return new Bmw();
}
}
public class Magnate{
public static void main(String[] args){
Factory factory = new BenzFactory();
Car car = factory.driverCar();
car.drive();
}
}
模板方法(Template Method)模式:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。
饭店中吃饭: 点菜,吃菜和买单三个步骤。点菜和买单基本上一致的,但是吃菜不同,吃法也不同。明确了一部分功能,而另一部分功能不明确。
public abstract class Hotel {
public void eat(){
System.out.println("点菜");
eatCai();
System.out.println("扫码买单");
}
//吃菜的方式
public abstract void eatCai();
}
public class QuanJuDe extends Hotel{
@Override
public void eatCai() {
System.out.println("拿饼");
System.out.println("放鸭肉,葱,黄瓜,甜面酱");
System.out.println("卷");
}
}
public class ZhangLiang extends Hotel{
@Override
public void eatCai() {
System.out.println("麻酱");
System.out.println("小米辣");
System.out.println("香菜");
System.out.println("蒜蓉");
System.out.println("蚝油");
}
}
public class Test {
public static void main(String[] args) {
QuanJuDe quanJuDe = new QuanJuDe();
quanJuDe.eat();
System.out.println("----------------------");
ZhangLiang zhangLiang = new ZhangLiang();
zhangLiang.eat();
}
}
Star接口
package com.itheima.demo02proxy;
//培养明星的接口
public interface Star {
//培养唱歌的方法
public abstract void changge();
//培养跳舞的方法
public abstract void tiaowu();
//培养演电影的方法
public abstract String yandianying(int money);
//培养吃饭的方法
public abstract void chifan();
//培养玩游戏的方法
public abstract void wanyouxi();
}
CaiXuKun类
package com.itheima.demo02proxy;
public class CaiXuKun implements Star {
@Override
public void changge() {
System.out.println("蔡徐坤在唱歌");
}
@Override
public void tiaowu() {
System.out.println("蔡徐坤在跳舞");
}
@Override
public String yandianying(int money) {
System.out.println("蔡徐坤在演电影"+money);
return "电影演完了";
}
@Override
public void chifan() {
System.out.println("和蔡徐坤一起吃饭");
}
@Override
public void wanyouxi() {
System.out.println("和蔡徐坤一起玩游戏");
}
}
WuYiFan类
package com.itheima.demo02proxy;
/*
ctrl+r:查找并替换
*/
public class WuYiFan implements Star {
@Override
public void changge() {
System.out.println("吴亦凡在唱歌");
}
@Override
public void tiaowu() {
System.out.println("吴亦凡在跳舞");
}
@Override
public String yandianying(int money) {
System.out.println("吴亦凡在演电影"+money);
return "电影演完了";
}
@Override
public void chifan() {
System.out.println("和吴亦凡一起吃饭");
}
@Override
public void wanyouxi() {
System.out.println("和吴亦凡一起玩游戏");
}
}
InvocationHandler接口的实现类:
package com.itheima.demo02proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/*
java.lang.reflect.InvocationHandler接口
InvocationHandler 是代理实例的调用处理程序 实现的接口。
用来生产代理人对象的动态代理接口
可以把被代理人(明星)传递到InvocationHandler接口的实现类中,让InvocationHandler接口的实现类生产明星的代理人
InvocationHandler接口的方法
Object invoke(Object proxy, Method method, Object[] args) 在代理实例上处理方法调用并返回结果。
作用:
使用invoke方法对被代理人(明星)的方法进行拦截,部分方法可以运行,部分方法不能运行
参数:
Object proxy:内部产生的代理人对象,不用管,自己创建
Method method:被拦截到的方法,invoke会对明星的方法(changge,tiaowu..)进行拦截,使用反射技术获取到这些方法,把方法赋值给method
Object[] args:被拦截到方法的参数,如果没有就是null->拦截到明星方法的参数
返回值:
Object:就是拦截到方法的返回值
注意:
在实现类中定义一个变量为Star类型
使用带参数构造方法,传递Star接口的实现类对象(明星),在类的内部生产明星的代理人对象
*/
public class InvocationHandlerImpl implements InvocationHandler {
//在实现类中定义一个变量为Star类型
private Star star;
/*
这样写,可以在使用Proxy.newProxyInstance时传递InvocationHandler实现类的
时候可以传递任何Start的实现类->多态
*/
public InvocationHandlerImpl(Star star) {
this.star = star;
}
/*
invoke方法对被代理人(明星)的方法进行拦截,部分方法可以运行,部分方法不能运行
调用明星changge,tiaowu,yandianying方法,可以运行
调用明星chifan,wanyouxi的方法,拦截不运行
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获取到方法名称
String methodName = method.getName();
//对方法名称进行判断
if("chifan".equals(methodName)){
throw new RuntimeException("不和你吃饭!");
}
if("wanyouxi".equals(methodName)){
throw new RuntimeException("不和玩游戏!");
}
/*
其他的方法,使用Method类的方法invoke让方法运行
invoke传递被代理的对象,这里我们传递start,因为star用来接收传递过来的实现类(被代理对象)
*/
Object v = method.invoke(star, args);
return v;
}
}
测试类:
package com.itheima.demo02proxy;
import java.lang.reflect.Proxy;
/*
动态代理:
创建代理人对象,对明星进行代理,对明星的方法进行拦截
调用明星changge,tiaowu,yandianying方法,可以运行
调用明星chifan,wanyouxi的方法,拦截不运行
想要实现动态代理:使用Proxy类生产代理人对象
java.lang.reflect.Proxy:
Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。
Proxy类中的静态方法:可以生产代理人对象
static Object newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h)
返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
参数:
ClassLoader loader:传递类加载器
Class>[] interfaces:传递被代理人实现的所有接口
InvocationHandler h:生产代理人的接口,传递InvocationHandler接口的实现类对象
返回值:
Object:返回的就是代理人对象
*/
public class Demo01Proxy {
public static void main(String[] args) {
//没有使用动态代理
CaiXuKun cxk = new CaiXuKun();
//cxk.changge();
//cxk.tiaowu();
//获取蔡徐坤的代理人对象
Star cxkProxy = (Star) Proxy.newProxyInstance(CaiXuKun.class.getClassLoader(),
CaiXuKun.class.getInterfaces(),new InvocationHandlerImpl(cxk));
cxkProxy.changge();
cxkProxy.tiaowu();
String s = cxkProxy.yandianying(100);
System.out.println(s);
//cxkProxy.chifan();
//cxkProxy.wanyouxi();
//获取吴亦凡的代理人对象
Star wyfProxy = (Star)Proxy.newProxyInstance(WuYiFan.class.getClassLoader(),
WuYiFan.class.getInterfaces(),new InvocationHandlerImpl(new WuYiFan()));
wyfProxy.changge();
wyfProxy.tiaowu();
wyfProxy.chifan();
}
}
演示Java已经实现的动态代理
package com.itheima.demo03proxy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/*
java.util.Collections:操作集合的工具类
static List unmodifiableList(List extends T> list)
返回指定列表的不可修改视图。
此方法允许模块为用户提供对内部列表的“只读”访问。
在返回的列表上执行的查询操作将“读完”指定的列表。
试图修改返回的列表(不管是直接修改还是通过其迭代器进行修改)
将导致抛出 UnsupportedOperationException:不支持操作异常,是RuntimeException
unmodifiableList方法的作用:传递List接口的实现类对象,方法内部对List接口的实现类对象进行代理,
返回一个被代理后的List接口的实现类对象(代理人对象)
unmodifiableList方法List接口进行代理之后
如果使用List接口中的size,get方法,没有修改集合的长度,则运行运行
如果使用add,remove,set方法,对集合进行了修改,则会抛出UnsupportedOperationException
*/
代码实现:
public class Demo01Proxy {
public static void main(String[] args) {
//创建List对象
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
//调用Collections中的方法unmodifiableList对List集合进行代理
List<String> listProxy = Collections.unmodifiableList(list);
//如果使用List接口中的size,get方法,没有修改集合的长度,则运行运行
System.out.println(listProxy.size());//3
System.out.println(listProxy.get(1));//b
//如果使用add,remove,set方法,对集合进行了修改,则会抛出UnsupportedOperationException
//listProxy.add("d");//UnsupportedOperationException
//listProxy.remove(0);//UnsupportedOperationException
listProxy.set(1,"www");//UnsupportedOperationException
}
}
需求:
模拟unmodifiableList方法,对List接口进行代理
调用List接口的方法会被拦截
如果使用的size,get方法,没有对集合进行修改,则允许执行
如果使用的add,remove,set方法,对集合进行了修改,则抛出运行时异常
分析:
1.定义一个代理方法proxyList
参数:传递List集合
返回值:被代理之后的List集合
2.方法内部可以使用Proxy类中的方法实现动态代理
代码实现:
package com.itheima.demo03proxy;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
/*
需求:
模拟unmodifiableList方法,对List接口进行代理
调用List接口的方法会被拦截
如果使用的size,get方法,没有对集合进行修改,则允许执行
如果使用的add,remove,set方法,对集合进行了修改,则抛出运行时异常
分析:
1.定义一个代理方法proxyList
参数:传递List集合
返回值:被代理之后的List集合
2.方法内部可以使用Proxy类中的方法实现动态代理
*/
public class Demo02Proxy {
public static void main(String[] args) {
//创建List对象
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
//调用proxyList方法,获取List接口的代理人对象
List<String> listProxy = proxyList(list);
System.out.println(listProxy.size());//3
System.out.println(listProxy.get(1));//b
listProxy.remove(0);//UnsupportedOperationException: remove no run
}
//1.定义一个代理方法proxyList
public static List<String> proxyList(List<String> list){
//2.方法内部可以使用Proxy类中的方法实现动态代理
List<String> listProxy = (List<String>)Proxy.newProxyInstance(Demo02Proxy.class.getClassLoader(),
list.getClass().getInterfaces(),new InvocationHandlerImpl(list));
return listProxy;
}
}
package com.itheima.demo03proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.List;
public class InvocationHandlerImpl implements InvocationHandler {
//定义一个List集合的变量
private List<String> list;
public InvocationHandlerImpl(List<String> list) {
this.list = list;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//invoke方法会获取list集合中的方法,在invoke方法内部对list集合的方法进行拦截
//获取方法的名称
String methodName = method.getName();
//如果使用的add,remove,set方法,对集合进行了修改,则抛出运行时异常
if("add".equals(methodName)){
throw new UnsupportedOperationException("add no run");
}
if("remove".equals(methodName)){
throw new UnsupportedOperationException("remove no run");
}
if("set".equals(methodName)){
throw new UnsupportedOperationException("set no run");
}
//如果使用的size,get方法,没有对集合进行修改,则允许执行
Object v = method.invoke(list, args);
return v;
}
}
动态代理案例流程图
动态代理非常的灵活,可以为任意的接口实现类对象做代理
动态代理可以为被代理对象的所有接口的所有方法做代理,动态代理可以在不改变方法源码的情况下,实现对方法功能的增强,
动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。
动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。
动态代理同时也提高了开发效率。
缺点:只能针对接口的实现类做代理对象,普通类是不能做代理对象的。