Java设计模式
一、单例模式
单例模式就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)
在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就 防止其它对象对自己的实例化,确保所有的对象都访问同一个实例
由于在系统内存中只存在一个对象,因此可以节约系统资源,当需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。
1.1单例模式:饿汉式(静态常量)
package com.gl.Singleton;
/*
单例模式:饿汉式(静态常量)
*/
public class SingletonTest01 {
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1 == instance2);
}
}
class Singleton{
//1.构造器私有化,外部不能new
private Singleton(){}
//2.本类内部创建对象实例
private final static Singleton instance = new Singleton();
//3.提供一个公有的静态方法,返回实例对象
public static Singleton getInstance(){
return instance;
}
}
1.2优缺点分析
优点:这种写法比较简单,就是在类装载的时候就完成了实例化,避免了线程同步问题。
缺点:在类装载的时候就完成了实例化,没有达到Lazy Loading的效果。如果从始至终未使用过这个实例,则会造成内存的浪费。
总结:这种单例模式可用,但可能会造成内存的浪费。
2.1单例模式:懒汉式
class Singleton1{
//单例模式:懒汉式
private static Singleton1 instance;
private Singleton1(){}
//提供一个静态的公有方法,当使用到该方法时,才去创建instance
//即饿汉式
public static Singleton1 getInstance(){
if (instance == null){
instance = new Singleton1();
}
return instance;
}
}
2.2优缺点分析
优点:起到了Lazy Loading的效果,调用的时候才创建对象
缺点:只能在单线程的情况下使用,如果有多个线程,可能在if判断语句出错
为了解决这个线程问题,可以有几种方法,像加个同步锁,或者使用静态内部类来解决
public static synchronized Singleton1 getInstance(){ //锁为这个类对象
if (instance == null){
instance = new Singleton1();
}
return instance;
}
class Singleton3{
private Singleton3(){}
//写一个静态内部类,该类中有一个静态属性
//利用内部类的特点,就是加载外部类时,不会自动加载内部类,用到时才加载,实现了懒加载
private static class SingletonInstance{
private static final Singleton3 instance = new Singleton3();
}
//提供一个静态的公有方法,返回instance
public static synchronized Singleton3 getInstance(){
return SingletonInstance.instance;
}
}
二、工厂模式
工厂模式也就体现了面向接口编程的思想
最开始的用法
interface Food{
void eat();
}
class Hamberger implements Food{
@Override
public void eat() {
System.out.println("我吃汉堡包");
}
}
//假设为服务器代码
public class SimpleFactoryTest {
public static void main(String[] args) {
Food food = new Hamberger();
food.eat();
}
}
//假设为客户端代码
//我们想要的是服务器的代码任意改变,客户端代码不用改变
这个用法就没有体现面向接口编程,同时扩展性不行,如果我想添加其他的食物,主程序就需要再重新添加类。
1.1简单工厂
interface Food{
void eat();
}
class FoodFactory{
public static Food getFood(int n){
Food food = null;
switch (n){
case 1:
food = new Hamberger();
break;
case 2:
food = new RiceNoddle();
break;
}
return food;
}
}
class Hamberger implements Food{
@Override
public void eat() {
System.out.println("我吃汉堡包");
}
}
class RiceNoddle implements Food{
@Override
public void eat() {
System.out.println("我吃米线");
}
}
public class SimpleFactoryTest {
public static void main(String[] args) {
Food food = FoodFactory.getFood(1);
food.eat();
}
}
现在它就简单符合了面向接口编程,如果服务器端修改了类名,客户端不需要修改。但是扩展性仍然不好,特别是当客户端需要扩展功能时,会修改服务器的代码(假设需要增加一个米饭)
1.2工厂模式
interface Food{
void eat();
}
interface Drink{
void Drinhk();
}
interface FoodFactory{
Food getFood();
}
class Hamberger implements Food{
@Override
public void eat() {
System.out.println("我吃汉堡包");
}
}
class HambergerFactory implements FoodFactory{
@Override
public Food getFood() {
return new Hamberger();
}
}
class RiceNoddleFactory implements FoodFactory{
@Override
public Food getFood() {
return new RiceNoddle();
}
}
class RiceNoddle implements Food{
@Override
public void eat() {
System.out.println("我吃米线");
}
}
class YouTiao implements Food{
@Override
public void eat() {
System.out.println("我吃油条");
}
}
class YouTiaoFactory implements FoodFactory{
@Override
public Food getFood() {
return new YouTiao();
}
}
//服务器
public class SimpleFactoryTest {
public static void main(String[] args) {
FoodFactory ff = new shupainFactory();
ff.getFood().eat();
}
}
通过这个工厂模式,如果需要在客户端新增食物,只需实现Food和FoodFactory接口就可以融入到服务器端中了,这样便解决了简单工厂中如果要新增食物便需要修改服务器端的代码,但是我们又考虑一个问题,如果我们现在要改需求,需要再添加一个饮料(即再增加一个工厂等级),这样的话需要建立的类就会成倍增长,需要建一个饮料类和一个饮料工厂,会显得很冗余,现在就介绍一下抽象工厂模式。
这个模式就比较适合不增加工厂等级,并且工厂等级少,客户端可能会增加同工厂等级的东西(像另外的一些食物)
工厂模式的UI类图
1.3抽象工厂模式
interface Food{
void eat();
}
interface Drink{
void drink();
}
interface Factory{
Food getFood();
Drink getDrink();
}
class KFCFactory implements Factory{
@Override
public Food getFood() {
return new Hamberger();
}
@Override
public Drink getDrink() {
return new Coca();
}
}
class SanQingFactory implements Factory{
@Override
public Food getFood() {
return new RiceNoddle();
}
@Override
public Drink getDrink() {
return new Coca();
}
}
class Coca implements Drink{
@Override
public void drink() {
System.out.println("喝可乐");
}
}
class Hamberger implements Food{
@Override
public void eat() {
System.out.println("我吃汉堡包");
}
}
class RiceNoddle implements Food{
@Override
public void eat() {
System.out.println("我吃米线");
}
}
class YouTiao implements Food{
@Override
public void eat() {
System.out.println("我吃油条");
}
}
public class SimpleFactoryTest {
public static void main(String[] args) {
Factory ff = new KFCFactory();
ff.getDrink().drink();
}
}
但是可以看到这个方法适合那种工厂等级比较多的情况。
抽象工厂声明了一组用于创建对象的方法,注意是一组(所以就比较适合那种工厂间有关系的情况)。像KFC是固定的,三秦套餐也是固定的。
比较适合的场景:
(1)和工厂方法一样客户端不需要知道它所创建的对象的类。
(2)需要一组对象共同完成某种功能时。并且可能存在多组对象完成不同功能的情况。
(3)系统结构稳定,不会频繁的增加对象。(因为一旦增加就需要修改原有代码,不符合开闭原则)
像螺母和螺丝这种场景就比较适合。
假设有6毫米螺丝,螺母和8毫米螺丝,螺母。生产时需要配套生产,就像上面示例的KFC套餐一样,有生产六毫米的工厂和生产八毫米的工厂。
三、原型模式
假设现在有一种情况就是我们需要每周写一次周报,周报内容有写报人,本周总结,下周计划,建议,时间。其实对于每周的内容来说,写报人,建议可能都不会咋变化,我们又不想去新建类,这个时候就可以用到原型模式
@Data
@AllArgsConstructor
@NoArgsConstructor
class WeekReport implements Cloneable{ //让这个类可以克隆
private String name;
private String summary;
private String plain; //下周计划
private String suggestion;
private Date time;
//重写克隆方法,让其可以被克隆
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Prototype {
public static void main(String[] args) throws CloneNotSupportedException, InterruptedException {
WeekReport w1 = new WeekReport("小猪","今天很好","无","无",new Date());
WeekReport w2 = (WeekReport) w1.clone(); //这样可以不需要依赖WeekReport类
Thread.sleep(1000);
w2.setTime(new Date());
System.out.println(w1);
System.out.println(w2);
}
}
结果为
这样就很好的实现了原型模式
总结:原型模式适用场景:
如果你需要复制一些对象, 同时又希望代码独立于这些对象所属的具体类, 可以使用原型模式。
其实说起也奇怪,之前跟着老师的代码来,它这个克隆方法是浅复制,修改了w1的Date,w2的Date也会随之改变。但是我自己试了一下,发现修改w1的,w2的并不会改变,然后我自己对比了一下源码,发现多了一个注释@HotSpotIntrinsicCandidate,可能这就是jdk14的一个小小的改进吧。