目录
1,什么是设计模式?
设计模式(英语 design pattern)是在软件设计中经常出现的问题的解决方案的经验总结。它们是被广泛接受和验证的,可以反复使用的设计思想和方法。设计模式提供了一种通用的语言和抽象概念,使得开发人员能够更好地沟通和理解彼此的设计意图。通过使用设计模式,开发人员可以更加灵活、可维护和可扩展的构建软件系统。常见的设计模式包括单例模式、工厂模式、观察者模式等。
2,设计模式主要解决什么问题?
对象的创建和管理:设计模式提供了各种方式来创建和管理对象,例如工厂模式、抽象工厂模式和建造者模式,它们可以帮助我们更灵活地实例化对象,并且隐藏了具体的实现细节。
对象之间的通信和协作:设计模式提供了一些方式来定义对象之间的交互方式,例如观察者模式、中介者模式和策略模式,它们可以帮助我们实现松耦合的对象之间的通信和协作。
对象行为的变化和扩展:设计模式提供了一些方式来处理对象行为的变化和扩展,例如模板方法模式、装饰器模式和状态模式,它们可以帮助我们通过组合和委托的方式来实现对象行为的灵活变化和扩展。
系统结构的组织和管理:设计模式提供了一些方式来组织和管理系统结构,例如组合模式、适配器模式和享元模式,它们可以帮助我们实现高度可组合、可扩展和易维护的系统结构。
通过灵活使用设计模式,开发人员可以更好地理解和应对软件设计中的挑战,提高代码的可复用性、可维护性和扩展性。
3,单例模式:
3.1,单例模式应用场景:
单例模式应用的场景一般发现在以下条件下: 资源共享的情况下,避免由于资源操作时导致的性能或损耗等,如日志文件,应用配置。
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,单例模式是一种对象创建型模式。
在我们开发中经常使用到的SpringBean就是单例模式,而@Resource和@Autowired都是作为注入容器时候会使用到的注解,但是其实@Resource并不是Spring的注解,但是为什么有相当一部分企业会使用此注解呢而并非Spring提供的@Autowired呢?跟我来看看吧。
3.2,@Autowired和@Resource
@Autowired注解是按照类型(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。
@Resource有两个重要的属性:name和type,而Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。
3.3,为什么选择@Resource?
@Autowired功能虽说非常强大,但是也有些不足之处。比如:比如它跟spring强耦合了,如果换成了JFinal等其他框架,功能就会失效。而@Resource是JSR-250提供的,它是Java标准,绝大部分框架都支持,所以这就是为什么部分企业在注入容器的时候选择@Resource。
3.4,单例模式代码:
在了解单例模式之前我们先设想一个场景, 假如你和你的小伙伴正在打暑假工但是你们突然看到, 地面脏了,你们正准备一人去拿一个拖把来拖地时,杀千刀的经理出现了,明明有那么多拖把不让你们用,非说:“等你们吧这个用坏了,再用别的拖把,这样可以省钱”,可以看到在这样一个场景下, 你们全部人使用到的确实是一个拖把,放置了浪费,但是效率却得不到很好的提升,因为当一个人在用拖把时,其他人必须得等他用完了,才能拿到拖把,所以实际上在使用单例模式的时候,一定要好好斟酌,好故事讲完了下面跟我来看看代码吧。
3.5,饿汉单例模式:
//饿汉单例模式
public class Clear1 {
//实例化清洁
private static Clear1 wash= new Clear1();
//构造方法
private Clear1() {}
//拖地方法
public void doWork(){
System.out.println("我正在使用Z拖把,拖地");
}
//每一个调用我方法的人,我都给他一个拖把
public static Clear1 getInstance(){
//wash.doWork();
return wash;
}
}
可以看到在上面的代码中,我们将CLear1对象添加了static关键字,并在类加载的时候就进行了实例化,所以在每一个调用getInstance()方法的人都可以获得一个新的实例(新的拖把)这就是饿汉模式,如果为了防止发生多个实例,可以使用锁的机制,接下来让我们看看正常创建实例会发生什么吧!
?怎么回事,我怎么创建不了对象了?哦,原来是我们将该对象的构造方法设置成了私有的,只有您在调用我自己的getInstance()方法才能正常实例化对象啊,接下来看看正确的代码吧。
public class Main1 {
public static void main(String[] args) {
//必须调用单例模式自己的方法才能正确获得对象实例
Clear1 instance = Clear1.getInstance();
//拿到拖把后,执行拖地方法
instance.doWork();
}
}
⚪执行结果:
我正在使用Z拖把,拖地
3.6,饱汉单例模式:
饱汉模式与饿汉模式最大的区别就是,不管你来多少人,我就这一个拖把,别人没用完你怎么滴都得给我等着,接下来看看饱汉单例模式代码吧。
//饱汉单例模式(双重检查锁)
public class Clear {
//并没有new一个新的实例,且添加volatile关键字来防止重排序
private volatile static Clear wash;
//构造方法
private Clear(){};
//清洁方法
public void doWork(){
System.out.println("我正在使用z拖把,拖地");
}
//只有确认拖把没人用,我才给你拖把
public static Clear getInstance(){
//进来先判断对象是否为空,如果为空才给你实例化对象
if (wash == null){
synchronized (Clear.class){
//第二次判断
if (wash == null){
wash = new Clear();
}
}
}
return wash;
}
}
到这里可能就有细心的人发现问题了,欸?为什么我们的对象没有new一个实例,还加了个volatile关键字?这是怎么回事,这恰恰就是饱汉模式的区别,不是每个调用我方法的人我都给你一个对象,我要做的就是保证有且仅有一个对象给你使用(拖把),而后面volatile关键字的作用仅仅是为了防止发生重排序,至于为什么会发生重排序呢?原来在我们new一个对象的时候,进行了三步操作:
1.分配内存
2.初始化对象
3.指向刚分配的内存
但是2步和3步由于cpu的优化,可能会产生排序,所以我们认为里是执行1-2-3,实际上它却是先执行3后执行2,这样就可能导致,假设 A 线程执行了1和3,还没有执行2,B线程来到判断 NULL,B线程就会直接返回还没初始化的instance了,而在此我们的volatile关键字恰好可以防止重排序。
接下来回到主函数,调用一下我们的饱汉单例模式看看结果把。
public class Main3 {
public static void main(String[] args) {
//执行双重检查锁单例模式
Clear instance = Clear.getInstance();
instance.doWork();
}
}
⚪输出结果:
我正在使用z拖把,拖地
4,工厂模式:
4.1,工厂模式应用场景:
当你在编写代码的过程中, 如果无法预知对象确切类别及其依赖关系时, 可使用工厂方法。
工厂方法将创建产品的代码与实际使用产品的代码分离, 从而能在不影响其他代码的情况下扩展产品创建部分代码。
例如, 如果需要向应用中添加一种新产品, 你只需要开发新的创建者子类, 然后重写其工厂方法即可。
4.2,工厂模式代码:
在了解工厂模式代码的时候我们继续来设想一个场景,假设我们现在有个超级工厂这个工厂主要的职责就是管理旗下的所有工厂,然后这个工厂旗下又有两个工厂,这两个工厂都是负责生产手机的,但是不同的是一个工厂负责生产昂贵的手机, 而另一个工厂负责生产性价比的手机,在这里我们又可以细分下,生产昂贵的手机分为两个品牌一个是苹果,一个是保时捷,而生产性价比的手机则是华为和小米,接下来请看代码。
4.4,管理生产的三大工厂:
SuperFactory(超级工厂):
//超级工厂,仅有doWork()方法
public interface SuperFactory {
public void doWork();
}
ExpensiveFactory(生产昂贵的手机的工厂):
//专门生产昂贵的产品的工厂,实现超级工厂并重写doWork()方法
public class ExpensiveFactory implements SuperFactory{
@Override
public void doWork() {
System.out.println("我专门做昂贵的手机");
}
}
AffordableFactory(生产性价比的手机的工厂):
//专门生产性价比产品的工厂,实现超级工厂并重写doWork()方法
public class AffordableFactory implements SuperFactory{
@Override
public void doWork() {
System.out.println("我专门做实惠的手机");
}
}
现在我们的类图如下:
然后继续回到我们上面说的,将两个工厂的品牌在进行划分。
4.5,昂贵工厂品牌:
AppleFactory(生产苹果手机的工厂):
//专门生产苹果的工厂,继承昂贵的工厂
public class AppleFactory extends ExpensiveFactory{
@Override
public void doWork() {
System.out.println("我生产苹果手机");
}
}
PorscheFactory(生产保时捷手机的工厂):
//专门生产保时捷手机的工厂,继承昂贵的工厂
public class PorscheFactory extends ExpensiveFactory{
@Override
public void doWork() {
System.out.println("我生产保时捷手机");
}
}
4.6,性价比工厂品牌:
HuaWeiFactory(生产华为手机的工厂):
//专门生产华为手机的工厂,继承性价比工厂
public class HuaWeiFactory extends AffordableFactory{
@Override
public void doWork() {
System.out.println("我生产华为");
}
}
XiaoMiFactory(生产小米手机的工厂):
//专门生产小米手机工厂,继承性价比工厂
public class XiaoMiFactory extends AffordableFactory{
@Override
public void doWork() {
System.out.println("我生产小米手机");
}
}
在最后完成一系列操作后我们的类图如下:
假如现在来了两个客户,A客户说:"我不差钱,你给我造最好的手机",而B客户说:"我资金没有那么多,你给我造性价比最高的手机",那么我们在主函数中可以这样写。
public class Main {
public static void main(String[] args) {
System.out.println("B客户:我想要生产性价比的产品");
SuperFactory affordableFactory = new HuaWeiFactory();
affordableFactory.doWork();
SuperFactory affordableFactory1 = new XiaoMiFactory();
affordableFactory1.doWork();
System.out.println("========================");
System.out.println("A客户:我想要生产昂贵的产品");
SuperFactory expensiveFactory = new AppleFactory();
expensiveFactory.doWork();
SuperFactory expensiveFactory1 = new PorscheFactory();
expensiveFactory1.doWork();
}
}
⚪输出结果:
B客户:我想要生产性价比的产品
我生产华为
我生产小米手机
========================
A客户:我想要生产昂贵的产品
我生产苹果手机
我生产保时捷手机
5,总结:
那么我究竟该如何选择设计模式呢?
选择模式的关键在于理解问题的本质和需求,并根据不同的情况选择合适的设计模式来解决问题。以下是一些选择设计模式的指导原则:
理解问题:首先要充分理解问题的需求和限制,明确问题的复杂性和规模,以便选择合适的设计模式。
设计目标:明确设计的目标,例如可维护性、可扩展性、可测试性等。不同的设计模式有不同的优势和适用场景,根据设计目标选择合适的设计模式。
设计原则:遵循设计原则,如单一职责原则、开闭原则、里氏替换原则等,可以帮助选择合适的设计模式。
经验和知识:积累设计模式的经验和知识,了解各种设计模式的特点和适用场景,可以帮助更好地选择合适的设计模式。
交互和协作:与团队成员或其他开发者进行讨论和交流,分享经验和意见,可以获得更多的建议和观点,有助于选择合适的设计模式。
总之,选择设计模式需要综合考虑问题本身、设计目标、设计原则、经验和知识等多个因素,灵活运用不同的设计模式来解决问题。