单例模式
单例模式本质就是不允许在通过new的方法再创建对象,这里也就不多废话,直接说单例模式的几种写法。首先单例模式可以分为懒汉式和饿汉式,饿汉式的意思是说,我们只要在类加载的过程中就会创建这么一个对象,外界有请求我们就直接返回,这种方式的优点是线程足够安全,但是缺点就是会浪费内存,因为这个创建的对象可能是不会用到的,为了解决这个问题就要用懒汉式的方法,懒汉式的优点就是,请求才会加载,不请求不会加载,但是单纯的懒汉式是线程不安全的,我们也必须要解决这个问题。先看2个基本的代码;
class User{
private static User user = new User();
private User(){}
public static User getUser(){
return user;
}
}
class User{
private static User user ;
private User(){}
public static User getUser(){
if (user==null){
user = new User();
}
return user;
}
}
懒汉式的问题就出现在getuser中的判断语句上,如果我们是一个多线程的系统,如果有3个线程同时到达getUser中,要进行对象的实例化,首先他们因为都进入了方法中,所以都会发现user == null ,那么就会各自创建新的对象并返回,这就导致我们的单例模式被破坏了(这个被破坏的现象应该只是会出现在第一次初始化中);所以我们需要加锁,先说第一种低效率的加锁方式,为整个getuser加上锁,这样我们3个线程也就会排队进入等待了,但是问题就是这个派对会影响到后续的所有线程,只要getUser就要排队,很慢的;所以我们只需要在 if 这个判断上进行加锁就行了。具体分析在代码
class User{
private static volatile User user ;
private User(){}
public static User getUser(){
//首先判断是不是没有初始化,如果初始化就直接返回,不会一直排队
if (user==null){
//假设这里有3个线程同时到达了,这里就需要他们3个处于排队状态,a线程进入,会进行判断,
//然后初始化,而后续的线程在进入的时候会发现user!=null ,就会直接返回唯一单例模式了;
synchronized (User.class){
if (user==null){
//只有第一个进来的才能实例化;
user = new User();
}
}
}
return user;
}
}
这应该是我们能够想到的最好的方式来处理单例模式了,但是还有一种best的方式来进行饿汉式加载,在Java中有一个特性就是,我们在使用一个类的时候,如果不去调用它的静态内部类中的方法,那么这个类是不会加载到内存中,所以我们可以把这个对象放在静态内部类中,当调用这个内部类,使其进行加载并返回对应的单例对象;
class User{
private User(){}
private static class Instance{
private static User user = new User();
public static User getInstance(){
return user;
}
}
public static User getUser(){
return Instance.getInstance();
}
}
这样既可以实现线程安全,也可以实现懒汉式加载了;
单例模式的保护措施‘
其实我们现在写的单例模式并不是真的单例模式,因为总会有方法破坏它,例如反射,破坏构造器,重新创建对象。或者序列化的反序列化的过程
public static void main(String[] args) throws IOException, ClassNotFoundException {
User user = User.getUser();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\12473\\Desktop\\计划.txt"));
oos.writeObject(user);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\12473\\Desktop\\计划.txt"));
User o = (User) ois.readObject();
System.out.println(o==user);
}
@Test
public void reflection() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
User user = User.getUser();
Class<User> userClass = User.class;
//因为是private,所以要获取到全部的构造器,记得爆破;
Constructor<User> constructor = userClass.getDeclaredConstructor();
constructor.setAccessible(true);
User user1 = constructor.newInstance();
System.out.println(user==user1);
}
如何解决这个问题呢?
对于序列化和反序列化中,我们可以声明一个readResolve的方法来告知对象流;
class User implements Serializable {
private User(){}
private static class Instance{
private static User user = new User();
}
public static User getUser(){
return Instance.user;
}
public Object readResolve(){
return Instance.user;
}
}
对于反射的解决办法,我们只能说在正常方式调用之后,是无法使用反射去调用构造器的,但是如果使用反射进行调用后,也是无法用正常方式去调用的;
class User implements Serializable {
private static boolean flag = false;
private User(){
synchronized (User.class) {
if (flag == true) {
throw new RuntimeException("不可以创建多个对象");
}
flag = true;
}
}
private static class Instance{
//这里已经调用了一次构造器!
private static User user = new User();
}
public static User getUser(){
return Instance.user;
}
public Object readResolve(){
return Instance.user;
}
}
抽象工厂模式
前言,在学习抽象工厂之前一定要理解什么是简单工厂和工厂模式
首先,我们要知道什么是产品族和产品等级,举个例子,鸿星尔克和李宁是2个服装的品牌,那他们就可以说是两个产品族,1.他们从事着相同的事业 2.他们都有相同的产品等级,这个产品等级我们可以理解成产品种类,例如 衣服 ,裤子 ,帽子 ,鞋子,等等,是有公共的特性的;而抽象工厂模式就可以理解成,我们有一个抽象的产品族,里面有各种的产品种类,就像一个特别大的工厂一样,货架已经设定好了,就等着品牌方入驻,更新货源了;
抽象工厂主要有4个角色 :
- 1.抽象的工厂,这是一个接口,为接下来的每一个实现该接口的实现类确定了最低标准,也就是说,如果你是服装工厂,那你最低得有衣服,裤子 , 鞋子,帽子这些产品;
- 2.具体的工厂,那就是入驻的商家,商家要把自己的货物放入对应的货架上,这样这家店才能开起来;
- 3.抽象产品:这是一个抽象类,可以理解为商品的生产车间,比如我们鸿星尔克要进衣服,李宁也要进衣服,但是仔细观察他们的产地都是温州,苏州的地方,所以这个工厂要从这个大的工厂中获取到自己的货源,
- 4.具体产品:具体产品就相当于我们买到的贴了牌子的产品,只不过是还没有走进官方店,等待着进货,发货,最后才到客户的手中
类图,另一家鸿星尔克同理;
再举一个最具体的例子,就是王者荣耀的皮肤系统,每一个角色都有自己的很多套套皮肤,但是我们可以看到,无论是那一款皮肤,都逃脱不出来的元素有:角色动作,角色的技能,角色的语音,角色的回城特效,虽然皮肤的外观是不同的,但是换汤不换药,就是一个产品族罢了;
文件结构图
//1.action 实现不同的动作
public abstract class action {
String actor = "夏侯惇";
public abstract void showaction();
}
//具体action类
public class actionA extends action{
@Override
public void showaction() {
System.out.println("无限飓风号回城中");
}
}
//skincolor 实现皮肤的名字?
public abstract class skincolor {
public abstract void showskincolor();
}
//具体类
public class skincolor1 extends skincolor{
@Override
public void showskincolor() {
System.out.println("我是无限飓风号");
}
}
//抽象工厂接口
public interface skinFactory {
action getaction();
skincolor getskincolor();
}
//具体工厂,一套一套的
public class skinfactory1 implements skinFactory{
@Override
public action getaction() {
return new actionA();
}
@Override
public skincolor getskincolor() {
return new skincolor1();
}
}
//最后是客户端实现
public class Actor {
public static void main(String[] args) {
skinfactory1 skinfactory1 = new skinfactory1();
skinfactory1.getskincolor().showskincolor();
skinfactory1.getaction().showaction();
}
}
简单工厂模式和工厂模式
原型模式
这模式没什么好说的,往上翻clone就好了,在Java中要实现原型模式,要保证的一个前提就是我们要实现Clonable接口,与此同时,我们也要区分好要进行深拷贝,还是浅拷贝,总的来说,只是一种获得对象的方式,对于深拷贝,我们可以通过对象流将要进行克隆的对象写进去然后再读出来,这就实现了破解,然后浅拷贝的克隆方式是不会走构造方法的,是已知大小的情况下进行对象的创建的;
建造者模式
建造者模式可以理解为一个类实例化对象的过程,我们根据不同的需求,可能要有不同的对象的属性,所以不在类的构造器中进行对象的实例化,而是拆分出来,这个模式,主要有3个对象,1,基本的类 (产品),2,builder(产品的不同的厂家) 3,director (生产车间),其中的builder可以抽象成一个接口,或者说是抽象类,来指定必须要生产的部件。
好的设计模式是耦合性非常低的,基本上都是这一层和下一层有着直接关系,而不会一层与多层发生联系;
在建造者模式中,可以这样去看,首先我们有一张电脑的设计图纸,上面给定了需要的部件,但是我们实际一定是需要去买零件的,我们得到了专业人士的建议,给了我们planA 和 planB,上面给好了我们要去买的零件清单,当我们买好了这些零件后,我们并不能立即得到这台电脑,我们需要带着这些零件去电脑城找一个人进行组装,他就会返回给我们一台我们计划中的电脑。这就是建造者模式。
public class product {
private String tire;
private String engine;
private String tank;
protected product(){}
public String getTire() {
return tire;
}
public void setTire(String tire) {
this.tire = tire;
}
public String getEngine() {
return engine;
}
public void setEngine(String engine) {
this.engine = engine;
}
public String getTank() {
return tank;
}
public void setTank(String tank) {
this.tank = tank;
}
@Override
public String toString() {
return "product{" +
"tire='" + tire + '\'' +
", engine='" + engine + '\'' +
", tank='" + tank + '\'' +
'}';
}
}
public abstract class builder {
//这里面有一个对象的;
protected product pro = new product();
abstract void buildertire();
abstract void builderengine();
abstract void buildertank();
public product returnproduct(){
return pro;
}
}
public class director {
builder builder;
public director(builder builder){
this.builder = builder ;
}
public product getproduct(){
builder.buildertire();
builder.buildertank();
builder.builderengine();
return builder.returnproduct();
}
}
这就是一个完整的建造者模式,通过三层,层层递进的方式完成了一个全新的对象实例。
拓展
建造者的另一种方法就是链式构造,有些类似与MP中的wrapper
public class phone {
private String p1;
private String p2;
private phone(builder builder){
this.p1 = builder.p1;
this.p2 = builder.p2;
}
public static class builder{
private String p1;
private String p2;
public builder setp1(String p1){
this.p1 = p1;
return this;
}
public builder setp2(String p2){
this.p2 = p2;
return this;
}
public phone build(){
phone phone = new phone(this);
return phone;
}
}
@Override
public String toString() {
return "phone{" +
"p1='" + p1 + '\'' +
", p2='" + p2 + '\'' +
'}';
}
}
代理模式
代理模式分为2种,一种是静态代理,一种是动态代理。
主要的角色有3个
1.抽象主题类(interface)用来做约束,里面声明的方法也是代理类要去代理的方法。
2.具体主题类(class) 代理的对象,代理类就是要去在具体类的方法的基础上增添色彩。
3.代理类 (class) 代理类,实现了抽象主题接口,并且聚合了具体主题类,能够对于具体主题中的方法进行增强
静态的代理类
public class proxy implements rental{
private tenant tenant;
public proxy(tenant tenant){
this.tenant = tenant;
}
@Override
public void rentalhouse() {
System.out.println("中介收取费用");
tenant.rentalhouse();
}
}
JDK动态代理:所谓的动态代理就是我们不会直接去创建一个代理类,而是当代码运行时创建一个运行时的增强类。在JDK动态代理中,我们主要要用到Java中自带的Proxy类,调用其newProxyInstance方法来获取一个动态代理类的对象,注意:下面的JdkProxy类不是真正的代理类,代理类是getProxy( )方法中的返回值,我们可以把这个我们自己定义的类认为就是一个代理的工厂,我们只需要传入不同的需要代理,(或者说就是需要增强)的对象,就可以去产生不同的代理类对象。在newProxyInstance( )中一共有3个参数,分别是我们需要去代理的类的类加载器,我们作为代理类要去实现的接口 ,以及一个InvocationHandler的匿名内部类 。
其中我们的这个InvocationHandler中实现的方法,就相当于我们在静态代理中重写的接口中的方法。在这个重写的invoke方法中,我们有有三个参数,这里的三个参数分别是 proxy 就是这个getProxy( ) 方法中的返回值,method就是代表了我们要执行的方法,这里的方法已经锁定就是我们传回去的接口中的方法,最后一个args 属于是接口方法中的参数,其实这些都不重要,我们的目标就是在这里调用我们的需要代理的方法,即method.invoke(“具体主题类”,args);这样这个代理类就会自主的完成其中的方法了,我们在客户端只需要借助这个代理工厂的返回值,调用其中的方法就行了。
如果接口中有多个方法会发生什么情况呢?
因为不论是具体主题类还是代理类都会实现所有的抽象主题的接口的方法,所以我们在创建新的代理对象的过程中就已经进行了所有接口方法的重写,这个重写,统一来说就是具体主题的方法(method.invoke(“具体对象类”,args)当然这也是可选择的)+我们的增强的方法,(也是可以选择的)。
所以如果说有多个方法最终结果都是 一个模版 即我们在newProxyInstance参数中重写的那个方法中的内容。
public class JdkProxy {
tenant tenant = new tenant();
public rental getProxy(){
rental proxy = (rental) Proxy.newProxyInstance(tenant.getClass().getClassLoader(), tenant.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("中介收取费用");
method.invoke(tenant,args);
return null;
}
});
return proxy;
}
}
public class Cilent {
public static void main(String[] args) {
JdkProxy jdkProxy = new JdkProxy();
rental proxy = jdkProxy.getProxy();
proxy.rentalhouse();
proxy.rentalcar();
}
}
CGilb动态代理
如果我们的具体主题类没有了接口,那么我们的JDK动态代理就会失效,所以CGLIB可以解决的就是动态代理问题
这是一个第三方的jar包,需要导入依赖,具体就不赘述了;
适配器模式
适配器模式大致思想,就是有一个适配器类,这个类会实现我们正常的接口规定的方法,但是这个具体的实现过程并不是由这个类自己进行编写,而是通过引入一个没有实现我们指定接口的类,注入到我们的适配器类中,然后去在我们的规定方法中,填充我们注入的对象的各个方法,可以说这个适配器类就是一个空壳,里面的所有的实现的细节都是我们注入的第三方类对象来进行实现的。
适配器类也分2类。一种是类适配器,着重与看类图;
在类适配器中,我们的适配器类要一方面实现官方组件的接口,同时要在另一方面去继承第三方的组件的实现类,多少有一些拆了东墙去补西墙的味道;拿着第三方组件写好的方法,去填补到官方的接口方法中。当然这样的结构设计是有一些问题,我们的适配器只能去继承这个指定的类,也就是直接写死了,继承了哪个类就是哪个类。
上面的问题也在对象适配器中得到了解决:我们不在采用继承的方式去实现官方指定的接口方法,而是采用注入依赖的方式来完成。
我们为适配器类中定义了一个第三方接口的实现类的属性。通过多态属性调用构造器来进行参数的传递。这样就可以一个适配器就能够接受不同的接口的实现类了。
桥接模式
桥接模式涉及多个维度的情况,可以避免类爆炸问题,经典的问题就是图形和颜色 不可能每一个图形和一个颜色就设计一个类,那基本就是n*m个类,太多了,在使用桥接模式后,就会成为m+n个类;
类图:
主要是有2个维度当然3个维度也未尝不可,其实就是如果有多个维度的类,要进行组合使用,我们就可以用桥接模式,将他们其中一个抽象为主类,其他的角色转换成接口,为这个主类进行增强!
组合模式
组合模式可以理解为我们有2个十分相同的对象,就比如公司的普通员工和总
享元模式
模版模式
·反向控制 父类执行子类的操作!