目录
设计模式是编程的一种理念、思想,不管是那种设计模式都遵循这设计模式的六大原则。如果要是要用一句话来概括的话那就是“用抽象封装框架、用实现拓展细节”,如果用一个词来概括的话那就是“解耦”。六大原则与设计模式我们需要遵守但是也不可矫枉过正,否则的话项目的代码将变得杂乱。
-
六大设计原则
- 单一职责:一个类只负责具体具体领域的一个职责,而不是实现很多的功能
- 依赖倒置:应该面向接口编程而不是面向类编程,定义一个实体的时候通过接口定义,再通过具体的类去实现,这样当类要替换的时候就不需要要再去改应用的地方,我们只需要重新定义一个类即可
- 迪米特法则:一个类、软件应该尽量的少于其他的类、软件发生依赖关系
- 接口隔离:要实现多接口而不是把这些方法都定义都一个接口中,换句话说就是不应该让哪些类去依赖不需要的接口。
- 里氏替换原则:这个原则要求我们如果继承父类的话不应该覆盖父类,这样容易造成Bug,如果有需要的话可以扩展父类。这样做是为了所有引用父类的地方必须能透明的使用其子类对象。
- 开闭原则:对扩展开放,对修改关闭。他也是对上述五个原则的一个概括,上述的五个原则从一定程度上来说也是为了实现这开闭原则。
-
工厂模式
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象(这一点应该就是依赖倒置原则的体现),实现了创建者和调用者分离。利用工厂模式可以降低程序的耦合性,为后期的维护修改提供了很大的便利。将选择实现类、创建对象统一管理和控制,从而将调用者跟我们的实现类解耦。
1、工厂模式在Spring中的应用:
Spring中的两大特性IOC、AOP,其中的IOC就是控制翻转,其中的一个理念就是工厂模式。在Spring中创建Bean对象的时候,使用了工厂模式,无论是通过Xml、配置类、还是注解,最终Spring的通过简单工厂模式来创建对象。在我们使用的时候用只需要问工厂那对应的实例即可,Spring工厂中拿到beanName与Class类型后就会通过动态反射技术创建一个对象将这个对象放在工厂中的一个静态map中,使用这个静态Map保证了单例行,避免反复创建带来的性能损耗。
Spring中之所以这样做是因为利用创痛的new来创建对象造成的耦合程度太高,例如A对象依赖B、B对象依赖C、C对象有依赖于D,这样的开发模式造成的结果就是对象满天飞,代码重复率高(每个调用就创建一个对象)。程序中的耦合可以分为方法间的耦合和对象间的耦合。
2、简单工厂
简单工厂中的角色主要分为工厂类角色、抽象产品类角色、具体产品类角色。其中的工厂类角色是这个模式的核心,他包含有一定的商业逻辑与判断逻辑,用来组装对象。
abstract class Audi {
//Audi汽车的抽象类
public Audi(){
}
}
public class AudiA6 extends Audi {
public AudiA6(){
System.out.println("这里生产的是奥迪A6");
}
}
public class AudiA8 extends Audi{
public AudiA8(){
System.out.println("这里生产的是奥迪A8");
}
}
public class AudiFactory {
public Audi createAudi(String type){
switch (type){
case "A6":
return new AudiA6();
case "A8":
return new AudiA8();
default:
System.out.println("当前暂无该车型的的生产能力");return null;
}
}
}
public class CustomerTest {
public static void main(String[] args) {
AudiFactory audiFactory = new AudiFactory();
Audi audiA6 = audiFactory.createAudi("A6");
Audi audiA8 = audiFactory.createAudi("A8");
}
}
3、方法工厂
工厂方法相对于简单工厂是又对工厂抽象了一层多了一个抽象工厂的角色。在方法工厂模式中抽象工厂角色是核心,他仅负责给出具体工厂角色需要实现的那些接口而不关心具体实现过程,他是具体工厂角色必须实现的接口或是必须实现的父类。在这个模式中体现着开闭原则,当有新的产品需要生产的时,只要按照抽象产品、抽象工厂的合同来进行生产那么就可以被客户端使用从而不需要修改任何已有的代码。
产品相关的代码与普通工厂的相同,这里省略
public interface IAudiFactory {
Audi createAudi();
}
public class AudiA6Factory implements IAudiFactory {
@Override
public Audi createAudi() {
System.out.println("这里是AudiA6造车场");
return new AudiA6();
}
}
public class AudiA8Factory implements IAudiFactory {
@Override
public Audi createAudi() {
System.out.println("这里是AudiA8造车厂");
return new AudiA8();
}
}
public class CustomerTest {
public static void main(String[] args) {
IAudiFactory audiFactoryA6 = new AudiA6Factory();
IAudiFactory audiFactoryA8 = new AudiA8Factory();
AudiA6 audiA6 = (AudiA6) audiFactoryA6.createAudi();
AudiA8 audiA8 = (AudiA8) audiFactoryA8.createAudi();
}
}
4、抽象工厂
抽象工厂简单的说就是工厂的工厂,个人认为他是在工厂方法上的有一层抽象。在这里抽象工厂可以创建创建具体工厂,具体工厂去生产具体的产品。
-
策略模式
策略模式就是定义一组算法,将这些算法分装起来使的算法可以随意切换,在策略模式中分为三个角色:封装角色、抽象策略角色、具体策略角色。封装角色中持有封装策略的句柄,抽象策略角色中规定了具体策略角色要实现的接口与属性。他的优势在于算法之间可以自由切换避免使用多重条件判断可扩展性非常好,但是他也会造成策略类增多,所有策略多需要对外暴露的缺点。
策略组:
public interface IStrategy {
void algorithm();
}
public class StrategyA implements IStrategy{
@Override
public void algorithm() {
System.out.println("这里是具体策略——>StrageA");
}
}
public class StrategyB implements IStrategy{
@Override
public void algorithm() {
System.out.println("这里是具体策略——>StrageB");
}
}
封装角色:
public class Context {
private IStrategy strategy;
public Context(IStrategy iStrategy){
this.strategy = iStrategy;
}
public void doStrategy(){
strategy.algorithm();
}
public IStrategy getStrategy() {
return strategy;
}
public void setStrategy(IStrategy strategy) {
this.strategy = strategy;
}
}
测试类:
public class StrageTest{
public static void main(String[] args) {
Context context = new Context(new StrategyA());
context.doStrategy();
context.setStrategy(new StrategyB());
context.doStrategy();
}
}
策略模式与工厂模式的综合应用
在下面的代码示例中策略与工厂的综合应用是终归中举的,在实际应用中或许我们可以将CashContent作为工厂的参数传递进去,同时传递进去的还有计算的类型,这样可以根据计算类型生产组装CashContent
//策略组
public interface ICashStrategy {
double calculateCash(double curCash);
}
class CashStrategyDiscount implements ICashStrategy {
@Override
public double calculateCash(double curCash) {
return curCash*0.8;
}
}
class CashStrategySpecialOffer implements ICashStrategy{
@Override
public double calculateCash(double curCash) {
return curCash>100?curCash-20:curCash;
}
}
//分装角色
public class CashContent {
ICashStrategy iCashStrategy = null;
public CashContent(){}
public CashContent(ICashStrategy cashStrategy){
this.iCashStrategy = cashStrategy;
}
public ICashStrategy getiCashStrategy() {
return iCashStrategy;
}
public void setiCashStrategy(ICashStrategy iCashStrategy) {
this.iCashStrategy = iCashStrategy;
}
public double doStetragy(double currCash){
return iCashStrategy.calculateCash(currCash);
}
}
//工厂策略类
public class StategyFactory {
public static CashContent productCashContent(String type) throws Exception {
switch (type){
case "满减":
return new CashContent(new CashStrategySpecialOffer());
case "打折":
return new CashContent(new CashStrategyDiscount());
default:
throw new Exception("展无当前活动");
}
}
}
//测试
//正常情况下
public static void main(String[] args) throws Exception {
CashContent cashContent = new CashContent();
String type = "满减";
double currCash = 100.2;
switch (type){
case "满减":
cashContent.setiCashStrategy(new CashStrategySpecialOffer());break;
case "打折":
cashContent.setiCashStrategy(new CashStrategyDiscount());break;
default:
throw new Exception("活动类型输入错误");
}
System.out.println("最终的结算金额为:"+cashContent.doStetragy(currCash));
}
//借助工厂模式的方法实现
@Test
public void factoryStrategyTest() throws Exception {
CashContent cashContent = StategyFactory.productCashContent("满减");
System.out.println("最终的结算金额为:"+cashContent.doStetragy(3920));
}
除过上面的使用方法,工厂与策略的结合也可以延伸出一种策略的高级玩法,就是制定策略的class文件,通过反射创建策略。这种方式也被用来一些高级软件中来实现自定的方法,在自己实现的方法中是要实现固定的接口与参数,下面是一种简单的实现:
@Test
public void advancedStrategyTest() {
ICashStrategy ICashStrategy = null;
//策略的路径
String path = "com.example.demo.designmodel.factoryandstrategy.strategy.CashStrategySpecialOffer";
try{
Class strategyClass = Class.forName(path);
Constructor strategyConstructor = strategyClass.getConstructor();
ICashStrategy = (com.example.demo.designmodel.factoryandstrategy.strategy.ICashStrategy) strategyConstructor.newInstance();
}catch (ClassNotFoundException e){
e.printStackTrace();
}catch (NoSuchMethodException e){
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
System.out.println("最终的结算金额为:"+ICashStrategy.calculateCash(1000));
}
-
单例模式
适用场景: 单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的 场合适合使用,如多个模块使用同一个数据源连接对象等等。如:
1. 需要频繁实例化然后销毁的对象。
2. 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
3. 有状态的工具类对象。
4. 频繁访问数据库或文件的对象。
饿汉模式
饿汉模式的好处是线程安全,使用简单,但是他的缺点也很明显:每次类加载的时候就会创建,如果要是这个类并不常用就会造成资源的浪费,同时因为对象的创建是在类加载时候进行的所以饿汉模式下是不能够传参的。饿汉模式的线程安全是因为static修饰的属性在类加载的时候就会进行初始化。
public class HungerSingleton {
private HungerSingleton(){}
private final static HungerSingleton sinleton = new HungerSingleton();
public HungerSingleton getInstance(){
return sinleton;
}
}
懒汉模式
利用匿名内部类可以保证线程的安全性,由于匿名内部类并不会在外部类加载的时候就加载所以他也可以避免资源的浪费。Holder的加载是发生在对getInstance方法调用的时候,jvm的机制规定了每个加载器下每个类只会被加载一次,如果要是多个线程对此类进行初始化,那么jvm的锁机制就会起作用保证只有一个可以成功。这样的机制保证线程的安全性但是带来的另一个问题就是如果在高并发的场景下,如果多个线程对类进行初始化,这时候就会造成较长时间的堵塞。
public class LazySingleton {
//将构造函数私有化保证只有他内部可以创建他
private LazySingleton(){};
public static LazySingleton getInstance(){
return Holder.SINGLE_TON;
}
private static class Holder{
private static final LazySingleton SINGLE_TON = new LazySingleton();
}
}
双重检查锁
public class DBCSingleton {
//volatile关键字保证读写一直,同时防止指令重排序
private static volatile DBCSingleton singleton = null;
private DBCSingleton(){}
public DBCSingleton getInstance(){
//这一层的非空判断是为了提高性能,如果没有这个判断,每一次对getInstance的访问都会枷锁
//频繁的加锁会极大的拖低性能
if (singleton==null){
synchronized (DBCSingleton.class){
//此处的非null判断,是为了避免DBCSingleton的多次创建打破单例
if (singleton==null){
singleton = new DBCSingleton();
}
}
}
return singleton;
}
}
为什么要使用volatile关键字?
指令重排序是指在执行程序时,为了提供性能,处理器和编译器常常会对指令进行重排序,但是 不能随意重排序,不是你想怎么排序就怎么排序,它需要满足以下两个条件:
1. 在单线程环境下不能改变程序运行的结果
2. 存在数据依赖的时候不能够进行重排序
例如在进行一个对象创建的时候,正常的顺序是分配内存空间、初始化对象、将对象指向刚分配的内存空间。但是有些编译器为了性能,可能为对第二步、第三步进行重排序顺序就形成了分配内存空间、将对象指向刚分配的内存空间、初始化对象。现在如果考虑重排序,两个线程发生以下的调用:
Time | ThreadA | ThreadB |
---|---|---|
T1 | 检查到singleton为空 | |
T2 | 获取锁 | |
T3 | 再次检查到singleton为空 | |
T4 | 为singleton分配内存空间 | |
T5 | 将singleton指向内存空间 | |
T6 | 检查到singleton不为空 | |
T7 | 访问singleton(此时对象还未完成初始化) | |
T8 | 初始化singleton |
虽然利用单例模式的机制通常情况下可以保证单例但是如果通过反射的机制类来进行对象的获取与创建那么单例的模式是会被破坏的,关于反射的使用可以参考:
Spring中单例模式的使用
下面的代码时Spring有关Bean创建注册的相关核心代码,其中应该关注的核心是如果是单例,且要创建单例时候的代码,他关于锁机制的应用,他是将锁加在了代码块上:
public class AbstractBeanFactory implements ConfigurableBeanFactory {
//充当了Bean实例的缓存,实现方式和单例注册表相同
private final Map singletonCash = new HashMap();
public Object getBean(String name)throws BeansException {
return getBean(name,null,null);
}
public Object getBean(String name,Class requiredType,Object[] args)throws BeansException{
//对传入的Bean name稍作处理,防止闯入的Bean name名有非法字符(或做转码)
String beanName = transformedBeanName(name);
Object bean = null;
//手工检测单例注册表
Object shareInstance = null;
//使用了代码输定同步块,原理和同步方法相似,但是这种的写法效率更高
synchronized (this.singletonCash){
shareInstance = this.singletonCash.get(beanName);
}
if(shareInstance!=null){
......
//返回合适的缓存Bean实例
bean = getObjectForSharedInstance(name,shareInstance);
}else{
......
//取得Bean的定义
RootBeanDefinition mergedBeanDefinition = getMergedBeanDefinition(beanName,false);
......
//根据Bean定义判断,此判断依据通常来自于组件配置文件中的单例属性开关
//<bean id = "date" class="java.util.Date" scope = "singleton"/>
//如果是单例则做如下处理
if(mergedBeanDefinition.isSingleton()){
synchronized (this.singletonCash){
//在此检查单例注册表
shareInstance = this.singletonCash.get(beanName);
if(shareInstance==null){
......
try{
//这其中体现着工厂模式的思想
shareInstance = createBean(beanName,mergedBeanDefinition,args);
//向单例注册表注册Bean实例
addSingleton(beanName,shareInstance);
}catch(Exception ex){
.......
}finally {
.......
}
}
}
bean = getObjectForSharedInstance(name,shareInstance);
}else {
//如果是非单例,每次创建都要创建一个Bean实例,这里没有想注册表注册吗,应该没有因为也没有什么意义
//<bean id= "date" class = "java.util.Date" scope="prototype"/>
bean = createBean(beanName,mergedBeanDefinition,args);
}
}
.........
return bean;
}
}
SpringMVC的Controller层默认是单例的所以在使用的时候,不要使用非静态的成员变量这样会造成代码的混乱:
在Spring中单例对象在容器创建的时候创建,容器只要一直存在对象就会一直存在,当容器销毁是对象也会销毁,总之就是对象与容器共存亡(但是也分饿汉模式与懒汉模式,这两者是有区别的)。多例对象是当我们使用对象的是Spring容器为我们创建,在对象使用的过程中对象就一直存在,当对象长时间不用的时候就会由JVM垃圾收集器进行收集,对象死亡。
工具类的技术选型:如果工具类中没有配置信息之类的话就使用静态类,如果要是存在配置信息(例如有过个数据源)这时候使用单例模式的好——(什么叫有配置信息?使用单例的模式是为了避免配置信息相关对象的创建吗?)