设计模式极简教程:单例模式(饿汉式,3种懒汉式,双重检查锁)静态变量,枚举,内部类,CAS。建造者 装修选款式和原型模式 题目和选项打乱

单例模式

基本整理

单例模式普通为8种,

  • 1饿汉,3懒汉
    • 饿汉
      • 就是 变量后 就直接new
    • 懒汉
      • 就是 在方法里new
      • 第二种很简答,方法上加synchronized,如HashTable
      • 第三种叫:双重检查锁
  • 静态变量,枚举,内部类,CAS

1. 极简 饿汉模式

public class Hungry {
    private static Hungry h = new Hungry();

    private Hungry() {
    }

    public static Hungry getInstance() {
        return h;
    }
}

2 和 3. 极简 懒汉模式

  • 因为 不想直接加载,所以 就放在方法里加载。
  • 所以就 新增了 bug,
  • 所以我们加锁去解决bug,
  • 加了锁,所以又出现了性能的问题,
  • 所以去解决性能的问题,有了下面的4,
  • 那么 我们为什么不用 懒汉模式呢?
    • 因为 不想直接加载,所以 就放在方法里加载。
    • 那别放在方法里啊,不想直接加载,就延迟加载。是不是就 没有什么 饿汉式,加锁饿汉,双重检查锁饿汉。
public class SimpleIdlerOrSync {
    
    private static SimpleIdlerOrSync s;

    private SimpleIdlerOrSync() {
    }

    public static SimpleIdlerOrSync getInstance() {
        //对象不为空,返回
        if (null != s) {
            return s;
        }

        s = new SimpleIdlerOrSync();
        
        return s;
    }

    //加锁
    public static synchronized SimpleIdlerOrSync getInstanceSync() {
        return getInstance();
    }
}

4. 饿汉之双重检查锁

  • 就是 把锁加在整个方法上,别人不满意

  • CPU为了性能,进行了指令重排,这一纳秒,恰好有多个人同时获得对象。

    • 没指令重排没问题,有了就可能出现。对象 指向堆内存的空间 后,我们就获取
    • 此时:对象还没有初始化,的问题。
  • 锁里面,锁外面都判断一下null

public class DoubleLockIdler {
    
    //保证 可见性。1,开辟内存空间,2,实例化参数,3,把对象 指向堆内存的空间。
    //jvm存在 指令重排。指向内存空间的时候,还没有初始化,dcl失效,拿对象 拿不到。
    //volatile禁止指令重排, 不是原子性,要 结合 synchronized关键字
    private static volatile DoubleLockIdler d;
    
    private DoubleLockIdler() {
    }

    public static DoubleLockIdler getInstance() {
        if (null != d) {
            //不为null,立刻返回
            return d;
        }
        synchronized (DoubleLockIdler.class) {
            //进入锁,再次校验
            if (null == d) {
                d = new DoubleLockIdler();
            }
        }
        return d;
    }
}

5. 定义一个 全局静态变量

public class SimpleStaticArgs {
    //jvm初始化的过程中,实例化属性。保存全局的对象
    public static Map<String, String> cache = new ConcurrentHashMap<String, String>();
}

6. 常用的枚举,都是单例

public enum SingletonEnum {
    INSTANCE;

    public void test() {
        System.out.println("hi");
    }
}

枚举统一继承Enum类

public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable

 javap -c xx.class //编译可看到

7. 内部类,都是单例

  • 提供一个内部类的返回
public class InnerClass {
    private InnerClass() {
    }

    //使用 内部类 点 返回外部类
    public static InnerClass getInstance() {
        return Holder.i;
    }

    //创建内部类
    private static class Holder {
        //提供 外部类的访问
        private static InnerClass i = new InnerClass();
    }
}

8. 使用CAS

(Compare-And-Swap)是比较并交换的意思,它是一条 CPU 并发原语,用于判断内存中某个值是否为预期值,如果是则更改为新的值,这个过程是原子的。

对应代码,Compare And Set

所谓原子操作类,

  • 指的是java.util.concurrent.atomic包下,
  • 一系列以Atomic开头的包装类。
  • 如AtomicBoolean,AtomicInteger,AtomicLong。
  • 它们分别用于Boolean,Integer,Long类型的原子性操作。
public class SingletonCas {

    private static final AtomicReference<SingletonCas> INSTANCE = new AtomicReference<>();

    //private static SingletonCas i;
    private SingletonCas() {
    }

    public static final SingletonCas getInstance() {
        for (; ; ) {
            //获取
            SingletonCas i = INSTANCE.get();

            //不为null返回
            if (null != i) {
                return i;
            }
            //为null设置
            INSTANCE.compareAndSet(null, new SingletonCas());
            //返回
            return INSTANCE.get();
        }//for循环
    }
}

在这里插入图片描述

1. 建造者 各种装修款式任你选

  • 方法参数和返回都是 同一个接口

基本材料,接口和实现

public interface Matter {

    /**
     * 场景;地板、地砖、涂料、吊顶
     */
    String scene();

    /**
     * 品牌
     */
    String brand();

    /**
     * 平米报价
     */
    BigDecimal price();
}
public class DerFloor implements Matter {

    @Override
    public String scene() {
        return "地板";
    }

    @Override
    public String brand() {
        return "德尔(Der)";
    }

    @Override
    public BigDecimal price() {
        return new BigDecimal(119);
    }

}
public class OneCeiling implements Matter {

    @Override
    public String scene() {
        return "吊顶";
    }

    @Override
    public String brand() {
        return "自家设计";
    }

    @Override
    public BigDecimal price() {
        return new BigDecimal(260);
    }
}

菜单接口和实现,参数和返回相同

public interface IMenu {
    /**
     * 吊顶
     */
    IMenu appendCeiling(Matter matter);

    /**
     * 地板
     */
    IMenu appendFloor(Matter matter);

    /**
     * 明细
     */
    String getDetail();
}
public class DecorationMenu implements IMenu {

    private List<Matter> list  = new ArrayList<>();  // 装修清单
    private BigDecimal   price = BigDecimal.ZERO;      // 装修价格

    private BigDecimal area;  // 面积
    private String     grade;     // 装修等级;豪华欧式、轻奢田园、现代简约

    //私有构造方法
    private DecorationMenu() {
    }

    //构造传递,面积 和 风格
    public DecorationMenu(Double area, String grade) {
        this.area  = new BigDecimal(area.toString());
        this.grade = grade;
    }

    //添加吊顶。参数是:基本装修物料。返回是本类
    @Override
    public IMenu appendCeiling(Matter matter) {
        list.add(matter);
        //区域 乘 区域 价格
        price = price.add(area.multiply(matter.price()));
        return this;
    }

    //添加地板
    @Override
    public IMenu appendFloor(Matter matter) {
        list.add(matter);
        price = price.add(area.multiply(matter.price()));
        return this;
    }

    @Override
    public String getDetail() {

        list.stream().forEach(v -> System.out.println(v.brand() + v.scene() + ":" + v.price() + "元"));

        System.out.println(grade + "共计:" + price + "元");

        return "1";
    }
}

建造者类,创建菜单接口的实现对象

  • 并且 进行测试
public class Builder {

    //参数为区域,创建 装饰包 类,多次调用其方法。
    public IMenu levelOne(Double area) {
        return new DecorationMenu(area, "豪华欧式")
                .appendCeiling(new OneCeiling())
                .appendFloor(new DerFloor());
    }

    public static void main(String[] args) {

        Builder builder = new Builder();

        // 豪华欧式
        builder.levelOne(10D).getDetail();
    }
}
自家设计吊顶:260元
德尔(Der)地板:119元
豪华欧式共计:3790.0元

2. 原型模式 试卷复制,题和选项打乱

  • 因为只有一题,所以 打乱暂时无意义
  • 答案为 JAVA2 HE,在每题中的选项不一样。
考生:花花[ChoiceQuestion(name=JAVA所定义的版本中不包括, option={A=JAVA2 HE, B=JAVA2 SE, C=JAVA2 Card, D=JAVA2 EE, E=JAVA2 ME}, key=A)]

考生:豆豆[ChoiceQuestion(name=JAVA所定义的版本中不包括, option={A=JAVA2 SE, B=JAVA2 Card, C=JAVA2 HE, D=JAVA2 EE, E=JAVA2 ME}, key=C)]

存题的类

@AllArgsConstructor
@Data
public class ChoiceQuestion {
    private String              name;                 // 题目
    private Map<String, String> option;  // 选项;A、B、C、D
    private String              key;
}
  • option里的 Map的 key,会被打乱。如原来是:
    • A:1 B:2 C:3 D:4 ,答案为:C:3,key为 C
    • A:4 B:2 C:1 D:3,答案为:D:3,key变为 D

选择 乱序工具类

public class TopicRandomUtil {
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class Topic {
		//这里name,题目并没有存储
        	
        private Map<String, String> option;  // 选项;A、B、C、D
        private String              key;           // 答案;B
    }

    /**
     * 乱序Map元素,记录对应答案key
     *
     * @param option 题目的选项。如:A——>1,B——>2,C——>3,D——>4 。
     * @param key    答案。如:C
     * @return Topic 乱序后的题 和 答案
     */
    static public Topic random(Map<String, String> option, String key) {
        //获取到。如:A B C D
        Set<String> keySet = option.keySet();

        //创建成 list,就是乱序后的
        ArrayList<String> keyList = new ArrayList<String>(keySet);
        //随机置换。如:ABCD,洗牌 变成:BACD
        Collections.shuffle(keyList);

        //创建 hashMap。因为是A B C D 的string,会自动排序
        HashMap<String, String> optionNew = new HashMap<String, String>();

        //乱序后选项,一个一个的取
        int    idx    = 0;
        //新的答案
        String keyNew = "";

        //遍历原来的 选项
        for (String next : keySet) {

            //洗牌后的选项,BACD,取第一个为 B
            String randomKey = keyList.get(idx++);

            //如果参数的 key(原本的答案) 和 next相同。原来的答案 遍历到了。
            //那么新的 答案,就是:现在这一个选项。原来 A B C D,答案为B。现在 BACD,答案A。
            //再次解释:只是 乱序了 key,key的选项没有乱序。那key乱序后,如何排好的,string 类型的A B C 自动排的
            if (key.equals(next)) {
                //新的正确答案,给记录上
                keyNew = randomKey;
            }
            //放入 到 新的map,用 新的选项 ,对应为:原来 A->1 ,现在要把 1 取出来。
            //原来的 选项一个一个遍历。现在放入是 用置换过的 一个一个遍历。
            //randomKey是 置换过得,结果key自动排序的时候,value 也响应的改变
            optionNew.put(randomKey, option.get(next));
        }
        return new Topic(optionNew, keyNew);
    }

}
    public static void main(String[] args) throws Exception {

        //创建 hashMap。因为是A B C D 的 string,会自动排序
        HashMap<String, String> h = new HashMap<String, String>();

        h.put("C", "11");
        h.put("B", "dfd");
        h.put("b", "dfd");
        h.put("D", "33");
        h.put("A", "gf");
        h.put("a", "dfd");

        System.out.println(h.toString());
        //{A=gf, a=dfd, B=dfd, b=dfd, C=11, D=33}  。A B C D ,key会自动排序好
    }

供拷贝的题库类

@Setter
public class QuestionBank implements Cloneable {

    private String candidate; // 考生
    private String number;    // 考号

    //选择
    private ArrayList<ChoiceQuestion> choiceQuestionList = new ArrayList<>();

    //选择题,加入到 选择题的列表。类似建造者
    public QuestionBank append(ChoiceQuestion choiceQuestion) {
        choiceQuestionList.add(choiceQuestion);
        return this;
    }


    @Override
    public Object clone() throws CloneNotSupportedException {
        
        //获取一个 clone对象
        QuestionBank questionBank = (QuestionBank) super.clone();
        //选择题 重新赋值。依然用 clone 方法
        questionBank.choiceQuestionList = (ArrayList<ChoiceQuestion>) choiceQuestionList.clone();

        // 题目乱序
        Collections.shuffle(questionBank.choiceQuestionList);

        // 获取所有的 选择题
        ArrayList<ChoiceQuestion> cList = questionBank.choiceQuestionList;
        
        //遍历 所有选择题
        for (ChoiceQuestion question : cList) {
            //使用 乱序方法
            TopicRandomUtil.Topic random = TopicRandomUtil.random(question.getOption(), question.getKey());
            //乱序后的 选项赋值
            question.setOption(random.getOption());
            //乱序后的 答案赋值
            question.setKey(random.getKey());
        }
        return questionBank;
    }

    @Override
    public String toString() {
        return "考生:" + candidate + choiceQuestionList.toString();
    }

}

题库管理,类似于建造者

public class QuestionManage {

    private QuestionBank questionBank = new QuestionBank();

    //构造 创建
    public QuestionManage() {
        questionBank.append(new ChoiceQuestion("JAVA所定义的版本中不包括", new HashMap<String, String>() {{
            put("A", "JAVA2 EE");
            put("B", "JAVA2 Card");
            put("C", "JAVA2 ME");
            put("D", "JAVA2 HE");
            put("E", "JAVA2 SE");
        }}, "D"))/*.append(new ChoiceQuestion("下列说法正确的是", new HashMap<String, String>() {{
            put("A", "JAVA程序的main方法必须写在类里面");
            put("B", "JAVA程序中可以有多个main方法");
            put("C", "JAVA程序中类名必须与文件名一样");
            put("D", "JAVA程序的main方法中如果只有一条语句,可以不用{}(大括号)括起来");
        }}, "A"))*/;
    }

    public static void main(String[] args) throws Exception {
		//当前类
        QuestionManage q = new QuestionManage();

        System.out.println(q.createPaper("花花", "1000001921032"));
        System.out.println(q.createPaper("豆豆", "1000001921051"));
    }

    public String createPaper(String candidate, String number) throws CloneNotSupportedException {
        //调用 clone的方法
        QuestionBank q = (QuestionBank) questionBank.clone();
        //考生姓名赋值
        q.setCandidate(candidate);
        q.setNumber(number);
        return questionBankClone.toString();
    }
}

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值