设计模式的六大原则
1.开放封闭原则
尽量通过扩展软件实体来解决需求变化,而不是通过修改已有的代码来完成变化
单例模式
1.什么是单例
- 保证一个类只有一个实例,并且提供一个全局访问点
那些地方用到了单例模式
- 网站的计数器,一般也是采用单例模式实现,否则难以同步。
- 应用程序的日志应用不用单例内容不好追加显示。
- 多线程的线程池的设计一般也是采用单例模式
- **Windows的(任务管理器)**就是很典型的单例模式
说说单例模式的应用(或其它模式的应用场景,或是给一个代码说出它的设计模式)
5.应用程序的日志应用,一般都用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加
6. Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。
7. 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。
8. 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
优点:
1.**单例模式在内存中只有一个实例 **,减少内存开支 ,确保所有的对象都访问一个实例
2.由于在系统内存中只存在一个对象,减少内存开支
3.避免对共享资源的多重占用
缺点
1.单例模式没有抽象层,扩展很困难
2.如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。
3.单例类在一定程度上违背了设计模式的单一责任原则(单一职责原则规定一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分)
单例模式使用注意事项:
-
使用时不能用反射模式创建单例,否则会实例化一个新的对象
-
使用懒单例模式时注意线程安全问题
-
饿汉式:类初始化时,会立即加载该对象,线程天生安全,调用效率高。
package com.lijie; //饿汉式 public class Demo1 { // 类初始化时,会立即加载该对象,线程安全,调用效率高 private static Demo1 demo1 = new Demo1(); private Demo1() { System.out.println("私有Demo1构造参数初始化"); } public static Demo1 getInstance() { return demo1; } public static void main(String[] args) { Demo1 s1 = Demo1.getInstance(); Demo1 s2 = Demo1.getInstance(); System.out.println(s1 == s2); } }
-
懒汉式: 类初始化时,不会初始化该对象,真正需要使用的时候才会创建该对象,具备懒加载功能。
package com.lijie;
//懒汉式
public class Demo2 {
//类初始化时,不会初始化该对象,真正需要使用的时候才会创建该对象。
private static Demo2 demo2;private Demo2() {
System.out.println(“私有Demo2构造参数初始化”);
}public synchronized static Demo2 getInstance() {
if (demo2 == null) {
demo2 = new Demo2();
}
return demo2;
}public static void main(String[] args) {
Demo2 s1 = Demo2.getInstance();
Demo2 s2 = Demo2.getInstance();
System.out.println(s1 == s2);
}
}
3.静态内部类
真正需要对象的时候才会加载,加载类是线程安全的。
由JVM保证线程安全且代码简单不容易出错
package com.lijie;
// 静态内部类方式
public class Demo3 {
private Demo3() {
System.out.println("私有Demo3构造参数初始化");
}
public static class SingletonClassInstance {
private static final Demo3 DEMO_3 = new Demo3();
}
// 方法没有同步
public static Demo3 getInstance() {
return SingletonClassInstance.DEMO_3;
}
public static void main(String[] args) {
Demo3 s1 = Demo3.getInstance();
Demo3 s2 = Demo3.getInstance();
System.out.println(s1 == s2);
}
}
4.枚举单例: 使用枚举实现单例模式 优点:实现简单、调用效率高,枚举本身就是单例
package com.lijie;
//使用枚举实现单例模式 优点:实现简单、枚举本身就是单例,由jvm从根本上提供保障!避免通过反射和反序列化的漏洞 缺点没有延迟加载
public class Demo4 {
public static Demo4 getInstance() {
return Demo.INSTANCE.getInstance();
}
public static void main(String[] args) {
Demo4 s1 = Demo4.getInstance();
Demo4 s2 = Demo4.getInstance();
System.out.println(s1 == s2);
}
//定义枚举
private static enum Demo {
INSTANCE;
// 枚举元素为单例
private Demo4 demo4;
private Demo() {
System.out.println("枚举Demo私有构造参数");
demo4 = new Demo4();
}
public Demo4 getInstance() {
return demo4;
}
}
}
双重检测锁方式 (因为JVM本质重排序的原因,可能会初始化多次,不推荐使用)
package com.lijie;
//双重检测锁方式
public class Demo5 {
private static Demo5 demo5;
private Demo5() {
System.out.println("私有Demo4构造参数初始化");
}
public static Demo5 getInstance() {
if (demo5 == null) {
synchronized (Demo5.class) {
if (demo5 == null) {
demo5 = new Demo5();
}
}
}
return demo5;
}
public static void main(String[] args) {
Demo5 s1 = Demo5.getInstance();
Demo5 s2 = Demo5.getInstance();
System.out.println(s1 == s2);
}
}
工厂模式
工厂模式分为简单工厂、工厂方法(核心)、抽象工厂模式
好处:不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象
代替new操作的一种模式。
利用工厂模式可以降低程序的耦合性
简单工厂:针对一种产品。缺点是破坏了开放,封闭原则。
工厂模式: 对简单工厂做了相应的改进,改正了简单工厂破坏开放封闭原则的错误。
抽象工厂:针对于多种产品,和简单工厂以及工厂模式并无太大的关联
Spring IOC
Spring IOC容器创建bean的过程是使用了工厂设计模式
当容器拿到了beanName和class类型后,动态的通过反射创建具体的某个对象,最后将创建的对象放到Map中,我们如果要对象就直接管工厂要就可以
1.简单工程
interface Computer {
public void printComputer();
}
class MacbookProComputer implements Computer {
public void printComputer() {
System.out.println("This is a macbook pro");
}
}
class SurfaceBookComputer implements Computer {
public void printComputer() {
System.out.println("This is a surface book");
}
}
class ComputerFactory {
public Computer createComputer(String type) {
Computer c = null;
if(type.equals("macbook")) {
c = new MacbookProComputer();
}else if(type.equals("surface")) {
c = new SurfaceBookComputer();
}
return c;
}
}
public class Client {
public void buy(Computer c){
System.out.println("I buy a computer");
c.printComputer();
}
public static void main(String[] args) {
Client c = new Client();
ComputerFactory cf = new ComputerFactory();
Computer computer = cf.createComputer("macbook");
c.buy(computer);
}
}
客户端就不需要感知具体对象是如何产生的,只需要将必要的信息提供给工厂即可
简单工厂模式是违反“开闭原则”,即对扩展开放,对修改关闭
2.工厂方法模式
interface Computer {
public void printComputer();
}
class MacbookProComputer implements Computer {
public void printComputer() {
System.out.println("This is a macbook pro");
}
}
class SurfaceBookComputer implements Computer {
public void printComputer() {
System.out.println("This is a surface book");
}
}
interface ComputerFactory {
public Computer createComputer();
}
class MsFactory implements ComputerFactory {
public Computer createComputer(){
return new SurfaceBookComputer();
}
}
class AppleFactory implements ComputerFactory {
public Computer createComputer() {
return new MacbookProComputer();
}
}
public class Client {
public void buy(Computer c){
System.out.println("I buy a computer");
c.printComputer();
}
public static void main(String[] args) {
Client c = new Client();
ComputerFactory cf = new AppleFactory();
Computer computer = cf.createComputer();
c.buy(computer);
}
}
工厂方法模式是针对每个产品提供一个工厂类,在客户端中判断使用哪个工厂类去创建对象
符合开闭原则
工厂模式横向扩展很方便,只需要创建相应的工厂类和产品类去实现抽象工厂接口和抽象产品接口
3.抽象工厂模式:
工厂方法模式和抽象工厂模式基本类似,可以这么理解:
当工厂只生产一个产品的时候,即为工厂方法模式,
而工厂如果生产两个或以上的商品即变为抽象工厂模式。
interface Computer {
public void printComputer();
}
class MacbookProComputer implements Computer {
public void printComputer() {
System.out.println("This is a macbook pro");
}
}
class SurfaceBookComputer implements Computer {
public void printComputer() {
System.out.println("This is a surface book");
}
}
interface OperatingSystem {
public void printSystem();
}
class MacOsSystem implements OperatingSystem {
public void printSystem() {
System.out.println("This is a mac os");
}
}
class Windows8System implements OperatingSystem {
public void printSystem() {
System.out.println("This is a window 8");
}
}
interface ProductionFactory {
public Computer createComputer();
public OperatingSystem createSystem();
}
class AppleFactory implements ProductionFactory {
public Computer createComputer() {
return new MacbookProComputer();
}
public OperatingSystem createSystem() {
return new MacOsSystem();
}
}
class MsFactory implements ProductionFactory {
public Computer createComputer() {
return new SurfaceBookComputer();
}
public OperatingSystem createSystem() {
return new Windows8System();
}
}
public class Client {
public void buy(Computer c){
System.out.println("I buy a computer");
c.printComputer();
}
public void use(OperatingSystem s) {
System.out.println("Operating System");
s.printSystem();
}
public static void main(String[] args) {
ProductionFactory pf = new AppleFactory();
Computer c = pf.createComputer();
OperatingSystem s = pf.createSystem();
Client client = new Client();
client.buy(c);
client.use(s);
}
}
Java的三种代理模式
1.代理模式-通过代理对象访问目标对象
好处:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.
思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法
1.1.静态代理
需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类.
/**
* 接口
*/
public interface IUserDao {
void save();
}
目标对象:UserDao.java
/**
* 接口实现
* 目标对象
*/
public class UserDao implements IUserDao {
public void save() {
System.out.println("----已经保存数据!----");
}
}
代理对象:UserDaoProxy.java
/**
* 代理对象,静态代理
*/
public class UserDaoProxy implements IUserDao{
//接收保存目标对象
private IUserDao target;
public UserDaoProxy(IUserDao target){
this.target=target;
}
public void save() {
System.out.println("开始事务...");
target.save();//执行目标对象的方法
System.out.println("提交事务...");
}
}
测试类:App.java
/**
* 测试类
*/
public class App {
public static void main(String[] args) {
//目标对象
UserDao target = new UserDao();
//代理对象,把目标对象传给代理对象,建立代理关系
UserDaoProxy proxy = new UserDaoProxy(target);
proxy.save();//执行的是代理的方法
}
}
1.可以做到在不修改目标对象的功能前提下,对目标功能扩展.
2.缺点:
- 因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护.
1.2.动态代理
JDK代理有以下特点:
1.代理对象,不需要实现接口
2.代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
3.动态代理也叫做:**JDK代理,**接口代理
代理类所在包:java.lang.reflect.Proxy
JDK实现代理只需要使用newProxyInstance方法
代理工厂类:ProxyFactory.java
/**
* 创建动态代理对象
* 动态代理不需要实现接口,但是需要指定接口类型
*/
public class ProxyFactory{
//维护一个目标对象
private Object target;
public ProxyFactory(Object target){
this.target=target;
}
//给目标对象生成代理对象
public Object getProxyInstance(){
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始事务2");
//执行目标对象方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事务2");
return returnValue;
}
}
);
}
}
测试类:App.java
/**
* 测试类
*/
public class App {
public static void main(String[] args) {
// 目标对象
IUserDao target = new UserDao();
// 【原始的类型 class cn.itcast.b_dynamic.UserDao】
System.out.println(target.getClass());
// 给目标对象,创建代理对象
IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();
// class $Proxy0 内存中动态生成的代理对象
System.out.println(proxy.getClass());
// 执行方法 【代理对象】
proxy.save();
}
}
代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理
1.2.1.Cglib代理
上面的静态代理和动态代理模式都是要求目标对象是实现一个接口的目标对象
目标对象只是一个单独的对象,并没有实现任何的接口,使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理
Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.
代码示例:
目标对象类:UserDao.java
/**
* 目标对象,没有实现任何接口
*/
public class UserDao {
public void save() {
System.out.println("----已经保存数据!----");
}
}
Cglib代理工厂:ProxyFactory.java
/**
* Cglib子类代理工厂
* 对UserDao在内存中动态构建一个子类对象
*/
public class ProxyFactory implements MethodInterceptor{
//维护目标对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//给目标对象创建一个代理对象
public Object getProxyInstance(){
//1.工具类
Enhancer en = new Enhancer();
//2.设置父类
en.setSuperclass(target.getClass());
//3.设置回调函数
en.setCallback(this);
//4.创建子类(代理对象)
return en.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("开始事务...");
//执行目标对象的方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事务...");
return returnValue;
}
}
测试类:
/**
* 测试类
*/
public class App {
@Test
public void test(){
//目标对象
UserDao target = new UserDao();
//代理对象
UserDao proxy = (UserDao)new ProxyFactory(target).getProxyInstance();
//执行代理对象的方法
proxy.save();
}
}
在Spring的AOP编程中:
如果加入容器的目标对象有实现接口,用JDK代理
如果目标对象没有实现接口,用Cglib代理
、
代理模式适合应用场景
延迟初始化 (虚拟代理):如果你有一个偶尔使用的重量级服务对象, 一直保持该对象运行会消耗系统资源时, 可使用代理模式。你无需在程序启动时就创建该对象, 可将对象的初始化延迟到真正有需要的时候。
访问控制 (保护代理):如果你只希望特定客户端使用服务对象, 这里的对象可以是操作系统中非常重要的部分, 而客户端则是各种已启动的程序 (包括恶意程序), 此时可使用代理模式。代理可仅在客户端凭据满足要求时将请求传递给服务对象。
本地执行远程服务 (远程代理):适用于服务对象位于远程服务器上的情形。在这种情形中, 代理通过网络传递客户端请求, 负责处理所有与网络相关的复杂细节。
记录日志请求 (日志记录代理):适用于当你需要保存对于服务对象的请求历史记录时。代理可以在向服务传递请求前进行记录。
缓存请求结果 (缓存代理):适用于需要缓存客户请求结果并对缓存生命周期进行管理时, 特别是当返回结果的体积非常大时。代理可对重复请求所需的相同结果进行缓存, 还可使用请求参数作为索引缓存的键值。比如请求图片、文件等资源时,先到代理缓存取,如果没有就去公网取并缓存到代理服务器
智能引用:可在没有客户端使用某个重量级对象时立即销毁该对象。代理会将所有获取了指向服务对象或其结果的客户端记录在案。代理会时不时地遍历各个客户端, 检查它们是否仍在运行。如果相应的客户端列表为空, 代理就会销毁该服务对象, 释放底层系统资源。代理还可以记录客户端是否修改了服务对象。其他客户端还可以复用未修改的对象
模板方法模式
基于继承的代码复用的基本技术
public abstract class AbstractTemplate {
/**
* 模板方法
*/
public void templateMethod(){
//调用基本方法
abstractMethod();
hookMethod();
concreteMethod();
}
/**
* 基本方法的声明(由子类实现)
*/
protected abstract void abstractMethod();
/**
* 基本方法(空方法)
*/
protected void hookMethod(){}
/**
* 基本方法(已经实现)
*/
private final void concreteMethod(){
//业务相关的代码
}
}
具体模板角色类,实现了父类所声明的基本方法
public class ConcreteTemplate extends AbstractTemplate{
//基本方法的实现
@Override
public void abstractMethod() {
//业务相关的代码
}
//重写父类的方法
@Override
public void hookMethod() {
//业务相关的代码
}
}
模板模式的关键:子类可以置换掉父类的可变部分,但是子类却不可以改变模板方法所代表的顶级逻辑。
模板方法中的方法可以分为两大类:模板方法和基本方法(三种,抽象方法(Abstract Method)、具体方法(Concrete Method)和钩子方法(Hook Method)。)。
模板方法模式在Servlet中的应用
httpService类提供了一个service()方法,这个方法调用七个do方法中的一个或几个,完成对客户端调用的响应。这些do方法需要由HttpServlet的具体子类提供,因此这是典型的模板方法模式。
HttpServlet担任抽象模板角色
模板方法:由service()方法担任。
基本方法:由doPost()、doGet()等方法担任。
TestServlet担任具体模板角色
TestServlet置换掉了父类HttpServlet中七个基本方法中的其中两个,分别是doGet()和doPost()。
AbstractQueuedSynchronizer也在其中运用了模板类
在父类中写好主要的功能模块,在子类中覆盖这些方法
equals ==
总结来说:
1)对于**==,如果作用于基本数据类型的变量,则直接比较其存储的 “值”是否相等**;
如果作用于引用类型的变量,则比较的是所指向的对象的地址
2)对于equals方法,注意:equals方法不能作用于基本数据类型的变量
如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;
诸如String、Date等类对equals方法进行了重写的话,比较的是所指向的对象的内容。