本文简要概述了主要的设计模式,并通过UML 和代码对其进行了详细解释。这篇文章总共大约2.5字,所以您可能需要保存它。本文的目录如下:
1. 理解设计模式
2.设计模式的分类
根据其目的
取决于范围
3. 设计模式的好处
4.设计模式要点
5、创作模型
简单(静态)工厂模式
工厂方法模式
抽象工厂模式
单例模式
原型图案
建造者模式
6. 个人经历
1. 理解设计模式
设计模式是我们的前辈经过大量的试验和错误而开发出来的针对软件开发过程中遇到的常见问题的解决方案。这些解决方案使用设计模式来重用代码,使其更容易被其他人理解,并确保代码的可靠性。
2.设计模式的分类
(1) 根据目的而定
换句话说,模式的用途可以分为三种类型:创意型、结构型和行为型。 创建模式主要用于创建对象。 结构模式主要用于处理类和对象的组合。 行为模式主要用于描述类或对象如何交互以及如何分配职责。
(2) 根据范围
换句话说,无论模式主要用于处理类之间的关系还是对象之间的关系,都可以分为两类:类模式和对象模式。类模式处理类和子类之间的关系。通过继承建立并在编译时编译。此刻是静止的。对象模式处理对象之间的关系。这些关系在运行时发生变化并变得更加动态。
3.设计模式的优点
可以提高程序员的思维、编程、设计能力。 使程序设计更加标准化,代码编辑更加工程化,大大提高软件开发效率,缩短软件开发周期。 设计的代码更具可重用性、可读性、可靠性、灵活性和可维护性。现在说这个肯定有点混乱,需要真正的发展才能实现真正的效益。
4.设计模式要点
(1)创建模式:简单工厂:工厂类根据接收到的参数决定创建产品类的哪个实例。工厂方法:定义一个用于创建对象的接口,并让子类决定实例化哪个类。
抽象工厂:创建一系列相关或依赖对象,而无需显式指定具体类。
建造者模式:封装了构建复杂对象的过程,并允许逐步构建它们。
单例模式:一个类只能有一个实例,提供全局访问点。
原型模式:通过复制现有实例来创建新实例。
(2)结构模型
发生模式:提供一个统一的方法来访问外部世界的子系统内的一组接口。
桥接模式:将抽象部分与其实现分开,允许它们独立修改。
组合模式:将对象组合成树形结构,以表示“部分-整体”的层次结构。
装饰模式:动态地为对象添加新功能。
代理模式:为其他对象提供代理来控制对该对象的访问。
适配器模式:将一个类的方法接口转换为客户端需要的另一个接口。
恒源(Fly Volume)模式:通过共享技术,有效支持大量细粒度对象。
(3)行为模式模板模式:定义算法结构并将一些步骤推迟到子类实现。
解释器模式:在给定语言中,定义其语法的表示并定义解释器。
策略模式:定义、封装一组算法并使它们可以互换。
一个简单的工厂有三个对象。 抽象产品类:提供抽象方法供具体产品类实现。 具体产品类:提供具体的产品。 工厂:根据内部逻辑返回对应的产品。
3. 代码实现
(1)抽象产品类别电话这可以是类、接口或抽象类。不要以固定的方式思考。我比较喜欢面向接口编程,所以这里使用了接口。
民众
界面
电话
{空格处
生产
()
;}
(二)具体产品类别
在此处插入您的代码片段并发布
班级
ApplePhoneImpl
实施
电话
{@覆盖
民众
空的空间
生产
()
{ System.out.println(\\\’苹果手机制造\\\’
);
民众
班级
RedmiiPhoneImpl
实施
电话
{@覆盖
民众
空的空间
生产
()
{ System.out.println(\\\’生成的红米手机\\\’
);
(3)工厂类
民众
班级
工厂
{民众
打电话
(字符串类型
)
{电话电话=
;如果
(\\\’红米\\\’。平等的
(类型)){ 手机=新手机
RedmiPhoneImpl() }其他;
如果
(\\\’苹果\\\’。平等的
(类型)){ 手机=新手机
ApplePhoneImpl(); }//.
返回
电话; }}
(4) 客户端使用
@测试公共
空的空间
测试1
()
{ 工厂工厂=新
手机redmiPhone=Factory.getPhone(\\\’Redmi\\\’
); 系统输出
.println(redmiPhone); redmiPhone.Produce(); 手机applePhone=Factory.getPhone(\\\’Apple\\\’
); 系统输出
.println(苹果手机);
执行结果如下。
4. 总结
优点:你可以简单地传递正确的参数来获取你想要的对象,而无需知道其创建的细节。
缺点:当添加新产品时,需要改变工厂类的决策逻辑,所以比如你想买华为手机,就需要添加华为手机。对于电话产品类,工厂逻辑也需要改变。
5.版本升级
通过反射创建对象改善了上面提到的缺点(添加新产品需要更改工厂类的决策逻辑),并且在添加新的特定产品时不再需要更改工厂中的代码。满足开闭原则。 (1)工厂类的代码如下。
民众
班级
工厂加
{民众
打电话
(班级班级)
扔
例外
{返回
(电话) Class.forName(clazz.getName()).newInstance();
(2)客户端代码如下:
@测试公共
空的空间
测试2
() 抛出异常
{ FactoryPlus 工厂=新
FactoryPlus(); 电话redmiPhone=Factory.getPhone(RedmiPhoneImpl.class);
.println(redmiPhone); redmiPhone.Produce(); 电话applePhone=Factory.getPhone(ApplePhoneImpl.class);
.println(苹果手机);
执行结果如下。
(3)好处总结:工厂类的方法逻辑采用反射机制来生成对象返回值。优点是添加产品时不必更改工厂类中的代码。满足开闭原则。
缺点:这种写法乍一看很棒,但仔细想想,它不仅存在反射效率的问题,还存在以下问题。 就我个人而言,我认为这不太好。调用不带参数的getName()).newInstance() 。构造函数创建一个与new Object() 具有相同属性的对象。如果需要使用参数初始化构造函数,则应使用工厂方法。就像工厂的工厂一样,没有任何实际意义。 2 如果不同的产品需要附加参数,则不支持。
6.再次升级(重要)
(1)工厂类:
民众
班级
工厂加加
{
/** * 习惯它! Spring ioc 通过在配置文件中设置以下语句,使用反射来创建对象。 * 这就是Spring IOC的原理。工厂+配置文件+反射。达到完全解耦的目的**/
私人的
静止的
String className=\\\’com.wander.design.simplefactory.product.ApplePhoneImpl\\\’
;民众
静止的
打电话
()
抛出异常
{返回
(电话) Class.forName(className).newInstance();
(2) 客户:
@测试
民众
空的空间
测试3
()
扔
例外
{电话phone=FactoryPlusPlus.getPhone();
(3)说明:Spring IOC容器的原理是:工厂+配置文件+反射,Spring读取配置文件(
)、获取className,利用反射机制Class.forName(className).newInstance()获取配置文件中分配给bean标签的id属性的对象的值。这是一家工厂。
优点:符合OCP原则,在不改变源码的情况下切换底层实现,达到隔离目的。
7.开发常用版本:Multimethod Factory
使用上述两种方法的工厂有两个缺点。一是不支持需要不同附加参数的不同产品。其次,如果使用时传递的类型或类不正确,将无法获取到正确的对象,容错性不高。
多方法工厂模型为每个产品提供了不同的生产方法,当你使用某个产品时,可以调用该产品的方法,使用方便且容错。
(1)工厂类的代码如下。
民众
班级
工厂其他方法
{
民众
静止的
在您的手机上安装Apple
()
{返回
新的
ApplePhoneImpl();
静止的
通过手机获取红米
()
{返回
新的
RedmiPhoneImpl(); }/**添加新的华为手机产品,只需在工厂中添加一个静态方法即可,无需修改原有方法**/
民众
静止的
在电话中获得荣誉
()
{返回
新的
HonorPhoneImpl() }};
(2) 客户端代码:
@测试
民众
空的空间
测试3
()
扔
例外
{ 手机苹果=FactoryMoreMethod.getApple(); 手机红米=FactoryMoreMethod.getRedmi(); 手机荣誉=FactoryMoreMethod.getHonor();
(3)应用场景:查看Java源代码。 java.util.concurrent.Executors 类是生成Executors 的工厂。使用多方法静态工厂模式。例如,ThreadPoolExecutor类构造方法有五个参数。其中三个的写法是固定的,前两个参数可以设置如下:
民众
静止的
ExecutorService newFixedThreadPool
(整数
n 个线程)
{返回
新的
ThreadPoolExecutor(nThreads, nThreads,0L
, TimeUnit.MILLISECONDS, 新
LinkedBlockingQueue());
再比如,如果JDK添加了一个创建ForkJoinPool类的方法,而你只想配置并行度参数,那么在该类中添加以下方法:
民众
静止的
ExecutorService newWorkStealingPool
(整数
并行处理
)
{返回
新的
ForkJoinPool(并行度, ForkJoinPool.defaultForkJoinWorkerThreadFactory,
, 真相
);
(4)总结:多方法工厂的优点是方便创建复杂的同类型参数对象。
八、应用场景
(1)凡是需要生成复杂对象的地方都可以使用工厂方法模式。任何可以直接使用new 执行的操作都不需要使用工厂模式。我个人的理解是这种复杂性(构造函数有很多参数)以及能否直接用new来使用很重要。
(2)客户端只知道传递给工厂类的参数,并不关心对象是如何创建的。 (升级后的简单工厂模式只需类名即可)
(3)由于工厂类创建的对象较少,因此工厂方法的业务逻辑不太复杂。 (升级后的简单工厂模式解决了这个问题,遵循开闭原则)
(4)在源码中使用简单工厂——日历:
日历cal=Calendar.getInstance(zone.toTimeZone(), locale);public
静止的
获取日历实例
(时区区域、区域设置aLocale
)
{返回
创建日历(区域,aLocale);
静止的
日历创建日历
(时区区域、区域设置aLocale
)
{“部分删除”
日历校准=
;如果
(aLocale.hasExtensions()) { String caltype=aLocale.getUnicodeLocaleType(\\\’ca\\\’
);如果
(剔除类型!=
) {转变
(剔除型){案例
\\\’佛教徒\\\’
:cal=新
BuddhaCalendar(zone, aLocale);break
;案件
\\\’日本人\\\’
:cal=新
JapaneseImperialCalendar(zone, aLocale);break
;案件
“格雷戈里”
:cal=新
GregorianCalendar(zone, aLocale);break
; } } }如果
(校准==
) {如果
(aLocale.getLanguage()==\\\’th\\\’
aLocale.get Country()==\\\’TH\\\’
) { 校准=新
佛历(Zone, aLocale) } else;
如果
(aLocale.getVariant()==\\\’JP\\\’
aLocale.getLanguage()==\\\’ja\\\’
aLocale.get Country()==\\\’JP\\\’
) { 校准=新
JapaneseImperialCalendar(zone, aLocale) } else;
{ 校准=新
GregorianCalendar(zone, aLocale) } }return;
卡路里}
(2)工厂方法模式
1. 知道
一句话,定义一个创建对象的接口,让子类决定实例化哪个类。这被称为工厂方法模式,因为当需要添加新产品时,需要添加特定的产品类及其对应的特定子工厂,然后在特定的子工厂方法中实例化该对象。
具体来说,我们定义一个工厂接口来创建对象,但将工厂方法留给实现子类来决定实例化哪个产品类。
工厂方法模型非常符合“开闭原则”,当需要添加新产品时,无需改变原有系统,就可以添加特定的产品类别及其对应的特定工厂。同时,使用Factory Method模式,用户只需要知道生产产品的具体工厂,而不需要知道产品的创建过程,甚至不需要知道具体的产品类名。
虽然很好地遵循了“开闭原则”,但每次添加新产品都需要添加两个类别,这不可避免地增加了系统的复杂性。
2.UML类图
UML描述:苹果手机和红米手机实现了手机抽象类,苹果工厂和红米工厂实现了抽象工厂。当然,苹果工厂要(依赖)生产苹果手机,当然红米工厂也生产。必须依赖Redmi来生成。如果顾客想要购买苹果手机,他们需要向苹果工厂索取苹果手机。如果客户想要购买红米手机,当然需要向红米工厂索取红米手机。
工厂方法有四个对象。 抽象产品类:为您实现的具体产品类提供的抽象方法。 抽象工厂:提供抽象方法供具体工厂实现。
3. 代码实现
(1)抽象产品类与简单工厂抽象产品类相同
(2)某些产品类别与简单工厂相同。
(3) 抽象工厂
p
乌布里克
界面
工厂
{电话获取电话
()
;}
(4) 指定工厂
民众
班级
苹果工厂实现
实施
工厂
{@覆盖
民众
打电话
()
{返回
新的
ApplePhoneImpl() }}公共
班级
RedmiFactoryImpl
实施
工厂
{@覆盖
民众
打电话
()
{返回
新的
RedmiPhoneImpl() }};
(5) 客户
@测试公共
空的空间
测试1
()
{ 工厂applePhoneFactory=新
AppleFactoryImpl(); 工厂redmiPhoneFactory=new
RedmiFactoryImpl(); 电话applePhone=applePhoneFactory.getPhone(); 电话redmiPhone=redmiPhoneFactory.getPhone();
.println(苹果手机);
.println(redmiPhone);
执行结果如下。
4. 总结
优点:用户只需要关心自己需要的产品的兼容工厂,而不需要担心细节。
完全支持开放和封闭原则,提高可扩展性。所谓开放封闭原则,就是对延伸开放,对变化封闭。白色的一点是,如果你实现了一个工厂方法并且想在将来扩展它,你不必改变原来的代码。您需要添加的只是工厂实现类和产品实现类。这一优势降低了因更改代码而引入错误的风险。
缺点: 每次添加产品时,都会创建特定的工厂类和特定的产品类,这往往会增加类的数量和复杂性。
抽象工厂和抽象产品增加了系统的抽象性和理解的难度。
5.工厂方法和简单工厂的区别
作为Factory Method模式的一个特点,我们可以看到,不仅是制造的产品,工厂也需要抽象。
工厂方法将产品类的实例化推迟到特定的工厂子类中。
工厂方法的优点是更灵活地接受变化。如果您的需求发生变化,只需添加或删除相应的类即可,无需修改现有的类。
简单工厂需要改变工厂类的方法,而多方法静态工厂模式需要添加静态方法。
缺点:引入抽象工厂层后,每次添加特定的产品类时,也必须同时添加特定的工厂类,所以每次添加特定的产品类时,都必须创建多方法静态首选工厂。工厂只需要添加新的静态方法。
六、应用场景
(1) 客户端不知道所需对象的类。 (需要知道需要什么对象的类使用升级后的简单工厂模式;需要知道需要什么参数的类使用简单工厂模式。)
(2) 抽象工厂类指定通过其子类创建的对象。
(3)在源码中使用简单工厂——Collections:集合(抽象工厂):
民众
界面
收藏
乙
延长
可重复的
乙
{ 迭代器迭代器
()
;}
ArrayList(具体工厂):
民众
班级
数组列表
{
民众
迭代器iterator() {返回
新的
itr(); }}
迭代器(抽象乘积):
民众
界面
迭代器
乙
{布尔值
还有下一篇
()
;}
ITR(特定产品):
私人的
班级
那
实施
迭代器
乙
{在
t
cursor; // index of next element to return
int
lastRet = -1
; // index of last element returned; -1 if no such
int
expectedModCount = modCount;public
boolean
hasNext
()
{return
cursor != size; }\’省略代码…\’
}
(3)抽象工厂模式
1.认识
①一句话来说就是,创建相关或依赖对象的家族,而无需明确指定具体类。因为我们可以定义具体产品类实现不止一个抽象工厂接口,一个工厂也可以生成不止一个产品类,是三个模式中较为抽象,并具一般性的模式。我们在使用中要注意使用抽象工厂模式的条件。
②所谓抽象工厂模式就是提供一个接口,用于创建相关或者依赖对象的家族,而不需要明确指定具体类。他允许客户端使用抽象的接口来创建一组相关的产品,而不需要关心实际产出的具体产品是什么。这样一来,客户就可以从具体的产品中被解耦。它的优点是隔离了具体类的生成,使得客户端不需要知道什么被创建了,而缺点就在于新增新的行为会比较麻烦,因为当添加一个新的产品对象时,需要更改接口及其下所有子类。
2.UML类图
UML说明:具体的苹果手机产品和具体的红米手机产品实现了手机产品抽象类,具体的苹果充电器产品和具体的红米充电器产品实现了充电器产品抽象类。具体的苹果工厂和具体的红米工厂实现了手机抽象工厂,然后苹果工厂生产苹果手机和苹果充电器,红米工厂生成红米手机和红米充电器。客户想要苹果手机和苹果充电器就要向苹果工厂要产品(对象),客户想要红米手机和红米充电器就要向红米工厂要产品(对象)。
工厂方法有四个对象:
①抽象产品类:为每种具体产品声明接口,如图中Phone手机抽象类和Charger充电器抽象类
②具体产品类:定义了工厂生产的具体产品对象,实现抽象产品接口声明的业务方法,如图中ApplePhoneImpl、RedmiPhoneImpl,AppleChargerImpl,RedmiChargerImpl
③抽象工厂:它声明了一组用于创建一种产品的方法,每一个方法对应一种产品,如上述类图中的Factory就定义了两个方法,分别创建Phone和Charger
④具体工厂:它实现了在抽象工厂中定义的创建产品的方法,生产一组具体产品,这组产品构件成了一个产品种类,每一个产品都位于某个产品等级结构中,如上述类图中的AppleFactoryImpl和RedmiFactoryImpl
3.代码实现
(1)抽象的产品
public
interface
Phone
{void
produce
()
;}public
interface
Charger
{void
produce
()
;}
(2)具体的产品
① 苹果具体的产品
public
class
AppleChargerImpl
implements
Charger
{@Override
public
void
produce
()
{ System.out.println(\\\”生产苹果充电器\\\”
); }}public
class
ApplePhoneImpl
implements
Phone
{@Override
public
void
produce
()
{ System.out.println(\\\”生产苹果手机\\\”
); }}
② 红米具体的产品
public
class
RedmiChargerImpl
implements
Charger
{@Override
public
void
produce
()
{ System.out.println(\\\”生产红米充电器\\\”
); }}public
class
RedmiPhoneImpl
implements
Phone
{@Override
public
void
produce
()
{ System.out.println(\\\”生产了红米手机\\\”
); }}
(3)抽象工厂
public
interface
Factory
{Phone getPhone
()
;Charger getCharger
()
;}
(4)具体的工厂
public
class
AppleFactoryImpl
implements
Factory
{@Override
public
Phone getPhone
()
{return
new
ApplePhoneImpl(); }@Override
public
Charger getCharger
()
{return
new
AppleChargerImpl(); }}public
class
RedmiFactoryImpl
implements
Factory
{@Override
public
Phone getPhone
()
{return
new
RedmiPhoneImpl(); }@Override
public
Charger getCharger
()
{return
new
RedmiChargerImpl(); }}
(5)客户端
@Testpublic
void
test1
()
{ Factory appleFactory = new
AppleFactoryImpl(); Phone applePhone = appleFactory.getPhone(); Charger appleCharger = appleFactory.getCharger(); System.out
.println(appleFactory); applePhone.produce(); appleCharger.produce(); Factory redmiFactory = new
RedmiFactoryImpl(); Phone redmiPhone = redmiFactory.getPhone(); Charger redmiCharger = redmiFactory.getCharger(); System.out
.println(redmiFactory); redmiPhone.produce(); redmiCharger.produce(); }
(5)执行结果
4.总结
优点:
①具体产品在应用层代码隔离,无须关系创建细节
②将一个系列的产品统一到一起创建
③对于增加新的产品族(一个具体工厂就是一个产品族),抽象工厂模式很好地支持了“开闭原则”,只需要增加具体产品并对应增加一个新的具体工厂,对已有代码无须做任何修改。
缺点:①规定了所有可能被创建的产品集合,产品族扩展新的产品(工厂中添加新的方法)困难。如果产品族扩展新的产品,需要修改所有的工厂角色,包括抽象工厂类,在所有的工厂类中都需要增加生产新产品的方法,违背了“开闭原则”。
②增加了系统的抽象性和理解难度
5.应用场景
抽象工厂在实际的开发中运用并不多,主要是在开发工程中很少会出现多个产品种类的情况,大部分情况使用以上两种工厂模式即可解决
6.个人总结
一句话总结工厂模式:方便创建 同种产品类型的 复杂参数 对象工厂模式重点就是适用于 构建同产品类型(同一个接口 基类)的不同对象时,这些对象new很复杂,需要很多的参数,而这些参数中大部分都是固定的,so,懒惰的程序员便用工厂模式封装之。(如果构建某个对象很复杂,需要很多参数,但这些参数大部分都是“不固定”的,应该使用建造者Builder模式)
(4)单例模式
1.认识
①一句话来说就是,某个类只能有一个实例,提供一个全局的访问点。
②单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。
③使用Singleton的好处还在于可以节省内存,因为它限制了实例的个数,有利于Java垃圾回收(garbage collection),而且确保所有对象都访问唯一实例。但是不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。用单例模式,就是在适用其优点的状态下使用
2.UML类图
UML说明:1.构造方法私有化:可以使得该类不被实例化即不能被new 2.在类本身里创建自己的对象 3.提供一个公共的方法供其他对象访问
3.代码实现
(1)饿汉式
①第一种:
public
class
Singleton
{/** * static: * ①表示共享变量,语意符合 * ②使得该变量能在getInstance()静态方法中使用 * final: * ①final修饰的变量值不会改变即常量,语意也符合,当然不加final也是可以的 * ②保证修饰的变量必须在类加载完成时就已经进行赋值。 * final修饰的变量,前面一般加static */
private
static
final
Singleton singleton = new
Singleton();/** * 私有化构造方法,使外部无法通过构造方法构造除singleton外的类实例 * 从而达到单例模式控制类实例数目的目的 */
private
Singleton(){}/** * 类实例的全局访问方法 * 因为构造方法以及被私有化,外部不可能通过new对象来调用其中的方法 * 加上static关键词使得外部可以通过类名直接调用该方法获取类实例 * @return
*/
public
static
Singleton getSingleton() {return
singleton; }}
②第二种
public
class
SingletonStatic
{
private
static
final SingletonStatic singletonStatic;/** * 和第一种没有什么区别,这种看起来高大上面试装逼使用 */
static
{ singletonStatic = new
SingletonStatic(); }private
SingletonStatic
()
{}public
static
SingletonStatic getSingletonStatic
()
{return
singletonStatic; }}
说明:①优点:一般使用static和final修饰变量(具体作用已经在代码里描述了),只在类加载时才会初始化,以后都不会,线程绝对安全,无锁,效率高。
②缺点:类加载的时候就初始化,不管用不用,都占用空间,会消耗一定的性能(当然很小很小,几乎可以忽略不计,所以这种模式在很多场合十分常用而且十分简单)
注:这里有两个小知识点:
a.如果是final非static成员,必须在构造器、代码块、或者直接定义赋值
b.如果是final static 成员变量,必须直接赋值 或者在静态代码块中赋值
(2)懒汉式
public
class
Singleton
{private
static
Singleton singleton =
;private
Singleton
()
{}public
static
Singleton getSingleton
()
{if
(singleton ==
){ singleton = new
Singleton(); }return
singleton; }}
说明:
①优点:在外部需要使用的时候才进行实例化,不使用的时候不会占用空间。
②缺点:线程不安全。看上去,这段代码没什么明显问题,但它不是线程安全的。假设当前有N个线程同时调用getInstance()方法,由于当前还没有对象生成,所以一部分同时都进入if语句new Singleton(),那么就会由多个线程创建多个多个user对象。
(3)线程安全的懒汉式
public
class
Singleton
{private
static
Singleton singleton;private
Singleton
()
{};private
static
synchronized
Singleton getSingleton
()
{if
(singleton ==
){ singleton = new
Singleton(); }return
singleton; }}
说明:①优点:解决了懒汉式线程不安全的问题 ②缺点:线程阻塞,影响性能。
(4)DCL单例 – 高性能的懒汉式
public
class
Singleton
{/*volatile在这里发挥的作用是:禁止指令重排序(编译器和处理器为了优化程序性能 * 而对指令序列进行排序的一种手段。) * singleton = new Singleton();这句代码是非原子性操作可分为三行伪代码 * a:memory = allocate() //分配内存,在jvm堆中分配一段区域 * b:ctorInstanc(memory) //初始化对象,在jvm堆中的内存中实例化对象 * c:instance = memory //赋值,设置instance指向刚分配的内存地址 * 上面的代码在编译运行时,可能会出现重排序从a-b-c排序为a-c-b。 * 重排序是为了优化性能,但是不管怎么重排序,在单线程下程序的执行结果不能被改变 * 保证最终一致性。而在多线程环境下,可能发生重排序,会影响结果。 * ①若A线程执行到代码singleton = new Singleton()时; * ②同时若B线程进来执行到代码到第一层检查if (singleton == ) * ③当cpu切换到A线程执行代码singleton = new Singleton();时发生了指令重排序, * 执行了a-b,没有执行c,此时的singleton对象只有地址,没有内容。然后cpu又切换到了B线程, * 这时singleton == 为false(==比较的是内存地址), * 则代码会直接执行到了return,返回一个未初始化的对象(只有地址,没有内容)。 * */
private
volatile
static
Singleton singleton;private
Singleton
()
{ }public
static
Singleton getSingleton
()
{/*第一层检查,检查是否有引用指向对象,高并发情况下会有多个线程同时进入 * ①当多个线程第一次进入,所有线程都进入if语句 * ②当多个线程第二次进入,因为singleton已经不为,因此所有线程都不会进入if语句, * 即不会执行锁,从而也就不会因为锁而阻塞,避免锁竞争*/
if
(singleton ==
) {/*第一层锁,保证只有一个线程进入, * ①多个线程第一次进入的时候,只有一个线程会进入,其他线程处于阻塞状态 * 当进入的线程创建完对象出去之后,其他线程又会进入创建对象,所以有了第二次if检查 * ②多个线程第二次是进入不到这里的,因为已被第一次if检查拦截*/
synchronized
(Singleton.class) {/*第二层检查,防止除了进入的第一个线程的其他线程重复创建对象*/
if
(singleton ==
) { singleton = new
Singleton(); } } }return
singleton; }}
说明:代码注释已详细讲解volatile在该单例模式的作用,已经双重锁的作用。①优点:解决了线程阻塞的问题 ②缺点:多个线程第一次进入的时候会造成大量的线程阻塞,代码不够优雅。
(5)静态内部类的方式
public
class
Singleton
{
private
Singleton
()
{}private
static
class
LayzInner
{
private
static
Singleton singleton = new
Singleton(); }public
static
Singleton getSingleton
()
{return
LayzInner.singleton; }}
说明:①优点:第一次类创建的时候加载,避免了内存浪费,不存在阻塞问题,线程安全,唯一性 ②缺点:序列化-漏洞:反射,会破坏内部类单例模式
(6)枚举单例模式
public
enum
EnumSingleton { INSTANCE;private
Singleton singleton; EnumSingleton(){ singleton = new
Singleton(); }public
Singleton getSingleton
()
{return
singleton; }}
说明:单元素的枚举类型已经成为实现Singleton的最佳方法,无法反射创建对象,但是特殊的饿汉式。
(7)静态内部类升级版
借鉴枚举单例的内部实现的方式
public
class
Singleton
{private
Singleton
()
{if
(LayzInner.singleton !=
){throw
new
RuntimeException(\\\”不能够进行反射!\\\”
); } }private
static
class
LayzInner
{private
static
Singleton singleton = new
Singleton(); }public
static
Singleton getSingleton
()
{return
LayzInner.singleton; }}
说明:①优点:第一次类创建的时候加载,避免了内存浪费,不存在阻塞问题,线程安全,唯一性,解决了反射会破坏内部类单例模式的问题
②缺点:不是官方的
(8)容器式单例
public
class
Singleton {private
Singleton() { }private
static
Map<String
, Object
> ioc = new
ConcurrentHashMap<>();public
static
Object
getBean(String
className) { synchronized (ioc) {if
(ioc.containsKey(className)) {Object
o =
;try
{ o = Class.forName(className).newInstance(); } catch
(Exception e) { e.printStackTrace(); }return
o; } else
{return
ioc.get(className); } } }}
说明:Spring ioc 单例 是懒汉式 枚举上的升级
(9)ThreadLocal单例
说明:局部单例模式:某一个线程里唯一
①ThreadLocal的作用呢,是提供线程内的局部变量,在多线程环境访问时,能保证各个线程内的ThreadLocal变量各自独立。也就是说每个线程的ThreadLocal变量是自己专用的,其他线程是访问不到的。
②ThreadLocal最常用于在多线程环境下存在对非线程安全对象的并发访问,而且该对象不需要在线程内共享,如果对该对象加锁,会造成大量线程阻塞影响程序性能,这时候就可以使用ThreadLocal来使每个线程都持有该对象的副本,这是典型的空间换取时间从而提高执行效率的方式。例如项目里经常使用的SimpleDateFormat日期格式化对象,该对象是线程不安全的,而且不需要在线程内共享,因此可以使用ThreadLocal保证其线程安全。
(5)原型模式
1.认识
①一句话来说就是,通过复制现有的实例来创建新的实例。因为是同过原有的对象创建新的对象,所以称为原型模式。
②原型模式是用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。原型模式允许一个对象再创建另外一个可定制的对象,无须知道任何创建的细节。
③用于创建重复的对象,同时又能保证性能。
(1)浅拷贝:我们只拷贝对象中的基本数据类型(8种),对于数组、容器、引用对象等都不会拷贝,只会拷贝对这些对象的引用。
(2)深拷贝:不仅能拷贝基本数据类型,还能拷贝那些数组、容器、引用对象(不仅拷贝对这些对象的引用,而且拷贝对象本身)。
2.UML类图
UML说明:实体类实现Cloneable接口,重写clone方法
3.代码实现
(1)Prototype类:
public
class
Prototype
implements
Cloneable
{
private
Integer id;private
String name;private
Map map
; @Overrideprotected
Prototype clone
()
throws CloneNotSupportedException
{//浅拷贝方式
Prototype prototype = (Prototype) super.clone();//深拷贝方式:对每一个复杂类型分别进行克隆
//测试浅拷贝的时候注释下面代码
prototype.map
= (Map) ((HashMap)this
.map
).clone();return
prototype; }public
Prototype
(Integer id, String name, Map map
)
{this
.id = id;this
.name = name;this
.map
= map
; }/**省略get、set方法和toString方法*/
}
(2)客户端:
public
class
Client
{
@Testpublic
void
test
()
throws CloneNotSupportedException
{ Map map
= new
HashMap<>();map
.put(\\\”数学\\\”
,100
D); Prototype prototype = new
Prototype(1
,\\\”小明\\\”
,map
); Prototype prototype1 = prototype.clone(); Map map1 = prototype1.getMap(); map1.put(\\\”数学\\\”
,99
d); System.out.println(prototype); System.out.println(prototype1); }}
(3)执行结果:浅拷贝:
改变其中一个对象map的值,两个对象的map内容都发生了变化深拷贝:
改变其中一个对象map的值,该对象的map内容发生了变化,另一个对象map的内容没有发生变化
4.总结
优点:①提高了性能,在需要短时间创建大量的对象和创建对象很耗时的情况下,原型模式比通过new对象大大提高了时间效率。
② 逃避构造函数的约束。
缺点:
1、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
2、实现原型模式每个派生类都必须实现 Clone接口。
5.应用场景
1.通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。比如,向数据库表插入多条测试数据,可以用到。
2.在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与Java融为浑然一体,大家可以随手拿来使用。
(6)建造者模式
1.认识:
①一句话来说:封装一个复杂对象的构建过程,并可以按步骤构造。因为需要对对象一步步建造起来,所以称为建造者模式。
②将复杂产品的构建过程封装分解在不同的方法中,使得创建过程非常清晰,能够让我们更加精确的控制复杂产品对象的创建过程,同时它隔离了复杂产品对象的创建和使用,使得相同的创建过程能够创建不同的产品。但是若内部变化复杂,会有很多的建造类。
2.UML类图:
UML说明:Product(产品角色):一个具体的产品对象。Builder(抽象建造者):创建一个Product对象的各个部件指定的抽象接口。ConcreteBuilder(具体建造者):实现抽象接口,构建和装配各个部件。Director(指挥者):构建一个使用Builder接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用,一是:隔离了客户与对象的生产过程,二是:负责控制产品对象的生产过程。
3.代码如下:
1.产品类:
public
class
Product {private
String
part1;//可以是任意类型
private
String
part2;private
String
part3;/**set get 方法省略}
2.抽象建造者
public
abstract
class
Builder
{ Product product = new
Product();public
abstract
void
buildPart1
()
;public
abstract
void
buildPart2
()
;public
abstract
void
buildPart3
()
;public
Product getResult
()
{return
product; };}
3.具体建造者
public
class
ConcreteBuilder
extends
Builder
{@Override
public
void
buildPart1
()
{ System.out.println(\\\”建造part1\\\”
); }@Override
public
void
buildPart2
()
{ System.out.println(\\\”建造part2\\\”
); }@Override
public
void
buildPart3
()
{ System.out.println(\\\”建造part3\\\”
); }}
4.指挥者:
public
class
Director
{
private
Builder builder;public
Director
(Builder builder)
{this
.builder = builder; }public
Product build
()
{ builder.buildPart1(); builder.buildPart2(); builder.buildPart3();return
builder.getResult(); }}
5.客户端
public
class
Client
{@Test
public
void
test
()
{ Builder builder = new
ConcreteBuilder(); Director director = new
Director(builder); director.build(); }}
6.执行结果
4.总结
优点:1、建造者独立,易扩展。将复杂产品的构建过程封装分解在不同的方法中,使得创建过程非常清晰,能够让我们更加精确的控制复杂产品对象的创建过程。
2、便于控制细节风险。它隔离了复杂产品对象的创建和使用,使得相同的创建过程能够创建不同的产品。
缺点:1、产品必须有共同点,范围有限制。
2、如内部变化复杂,会有很多的建造类,导致系统庞大。
应用场景1、需要生成的对象具有复杂的内部结构。2、需要生成的对象内部属性本身相互依赖。
5.应用场景
JAVA 中的 StringBuilder。
六、个人体会
设计模式是一种解决问题的思维和方式,不要生搬硬套,为了设计模式而模式。本文作者:王德印,欢迎复制下方链接关注博主动态。
链接:https://blog.csdn.net/qq_41889508/article/details/105953114
本文和图片来自网络,不代表火豚游戏立场,如若侵权请联系我们删除:https://www.huotun.com/game/591132.html