今日主题:开发原则和设计模式
一、设计模式
1、什么是设计模式?
设计模式(Design Pattern)是一套被反复使用,多数人知晓的,经过分类总结的代码设计经验。
菜鸟和高手的区别在于高手受过很多伤(之前犯过很多错,踩过很多坑),这些高手不想新人也去
继续犯他们的错误,就把他们的开发经验总结出来,体现在代码设计方面就是设计模式。
GOF又称Gang of Four:四人帮
(即Erich Gamma, Richard Helm, Ralph Johnson & John Vlissides),
《设计模式:可复用面向对象软件的基础》提出了23种设计模式。
2、为什么设计模式这么厉害?
(1)设计模式不仅仅是针对一种编程原因的,适用于所有编程语言的一套经验。
C,Java,Python等都可以用这套经验。
(2)考虑了代码的
可重用性:高
可读性:易读
可扩展性:高
可靠性:高
…
总的一句话:高内聚低耦合。
3、学习设计模式有几个阶梯?
第1层:刚开始学编程不久,听说过什么是设计模式
第2层:有很长时间的编程经验,自己写了很多代码,其中用到了设计模式,但是自己却不知道
第3层:学习过了设计模式,发现自己已经在使用了,并且发现了一些新的模式挺好用的
第4层:阅读了很多别人写的源码和框架,在其中看到别人设计模式,并且能够领会设计模式的精妙和带来的好处。
第5层:代码写着写着,自己都没有意识到使用了设计模式,并且熟练的写了出来。
二、开发原则
1、开发原则是干什么的?
保证代码高内聚低耦合,使得代码有高扩展性、高复用性、高可读性、高灵活性等等。
设计模式就是遵循开发原则的。
2、有哪些开发原则呢?(面向对象的开发原则)
1、开闭原则【Open Close Principle,缩写是OCP】:对修改关闭对扩展开发。
例如: 小明去医院看病,医生开了阿司匹林药花了20元, 第二天和朋友聚会聊起这事,
小红说道:不对呀,前几天我在医院也拿了阿司匹林药,才 14 块钱呢。
小花说:奇怪了,我买的是 16 块钱。小杰回应:怎么我买的是 18 块。
怎么这药这么多个价格。
问题的原因:现价格跟社保有关。
小明没有社保,小红社保是一档,小花社保是二挡,小杰社保是三挡。
如何开发这样的医院的收费系统呢?
如果社保等级有变化,不止1-3挡了。还要增加4,5等等。
涉及到修改Patient的pay方法。违反了对修改关闭的原则。
为了保证代码的扩展性,对代码重新设计。
因为主要是由于病人不同(他们的社保等级不同),导致药品的价格不同,
那么我们可以把病人分为很多类。
病人的类型:一等社保病人,二等社保病人。。。
Patient:子类(OneLevelSocialSecurityPatient、TwoLevelSocialSecurityPatient…)
public class TestPrinciple1 {
//程序入口
public static void main(String[] args) {
//创建不同的病人
Patient ming = new Patient("小明");
Patient hong = new Patient("小红",1);
Patient hua = new Patient("小花",2);
Patient jie = new Patient("小杰",3);
Hospital hospital = new Hospital();
hospital.saleMedicine(ming);
hospital.saleMedicine(hong);
hospital.saleMedicine(hua);
hospital.saleMedicine(jie);
}
}
//药物类型
//药物的熟悉:名称、价格、批次、厂家、疗效....
class Medicine {
private String name;
private double price;
public Medicine(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
}
//接口,针对病人的处理接口
//这个医院的收费系统,针对病人需要提供什么服务呢?
//(1)获取病人的姓名
//(2)需要付多少钱,针对什么药
interface IPatient{
String getName();
double pay(Medicine medicines);
}
//病人:关注两个属性:姓名,社保等级
class Patient implements IPatient{
private String name;
private int level;//默认值是0
//如果使用这个构造器,表示病人的社保等级是0,即没有社保
public Patient(String name) {
this.name = name;
}
//手动指定社保等级
public Patient(String name, int level) {
this.name = name;
this.level = level;
}
@Override
public double pay(Medicine medicines) {
if (level == 1) {
return medicines.getPrice()*0.7;
} else if (level == 2) {
return medicines.getPrice()*0.8;
} else if (level == 3) {
return medicines.getPrice()*0.9;
}
return medicines.getPrice();
}
@Override
public String getName() {
return name;
}
}
//医院系统的管理类型
class Hospital {
//这里用其中一个药品做演示
private Medicine medicine = new Medicine("阿司匹林", 20.0);
//卖药,根据病人的不同,卖药的处理不一样
public void saleMedicine(IPatient patient) {
double money = patient.pay(medicine);
System.out.println(patient.getName() + " 花了 " + money + "块钱买了药:" + medicine.getName());
}
}
如果此时增加社保等级,还需要修改原来的类吗?
不需要修改原来的类,只需要增加一个子类即可。
对修改关闭,对扩展开放。
public class TestPrinciple1_2 {
public static void main(String[] args) {
//创建不同的病人
Patient ming = new Patient("小明");
Patient hong = new OneLevelSocialSecurityPatient("小红");
Patient hua = new TwoLevelSocialSecurityPatient("小花");
Patient jie = new ThreeLevelSocialSecurityPatient("小杰");
Hospital hospital = new Hospital();
hospital.saleMedicine(ming);
hospital.saleMedicine(hong);
hospital.saleMedicine(hua);
hospital.saleMedicine(jie);
}
}
//药物类型
//药物的熟悉:名称、价格、批次、厂家、疗效....
class Medicine {
private String name;
private double price;
public Medicine(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
}
//接口,针对病人的处理接口
//这个医院的收费系统,针对病人需要提供什么服务呢?
//(1)获取病人的姓名
//(2)需要付多少钱,针对什么药
interface IPatient{
String getName();
double pay(Medicine medicines);
}
//病人:关注两个属性:姓名,社保等级
class Patient implements IPatient {
private String name;
private int level;//默认值是0
//编译器不会自动增加无参构造了
//如果使用这个构造器,表示病人的社保等级是0,即没有社保
public Patient(String name) {
this.name = name;
}
//手动指定社保等级
public Patient(String name, int level) {
this.name = name;
this.level = level;
}
@Override
public String getName() {
return name;
}
@Override
public double pay(Medicine medicines) {
//默认的病人类型,返回药品的原价
return medicines.getPrice();
}
}
//OneLevel:一等
//SocialSecurity:社保
//子类继承父类时,一定会在子类的构造器的首行,自动调用父类的无参构造,
// 如果父类没有无参构造,那么需要手动调用父类的有参构造
class OneLevelSocialSecurityPatient extends Patient{
public OneLevelSocialSecurityPatient(String name){
super(name,1);
}
@Override
public double pay(Medicine medicines) {
//一等的病人类型,返回药品的原价
return medicines.getPrice()*0.7;
}
}
class TwoLevelSocialSecurityPatient extends Patient{
public TwoLevelSocialSecurityPatient(String name){
super(name,2);
}
@Override
public double pay(Medicine medicines) {
//二等的病人类型,返回药品的原价
return medicines.getPrice()*0.8;
}
}
class ThreeLevelSocialSecurityPatient extends Patient{
public ThreeLevelSocialSecurityPatient(String name){
super(name,3);
}
@Override
public double pay(Medicine medicines) {
//三等的病人类型,返回药品的原价
return medicines.getPrice()*0.9;
}
}
//医院系统的管理类型
class Hospital {
//这里用其中一个药品做演示
private Medicine medicine = new Medicine("阿司匹林", 20.0);
//卖药,根据病人的不同,卖药的处理不一样
public void saleMedicine(IPatient patient) {
double money = patient.pay(medicine);
System.out.println(patient.getName() + " 花了 " + money + "块钱买了药:" + medicine.getName());
}
}
2、单一职责原则【Single Responsibility Principle,缩写是SRP】:
小到一个方法,往大了说,一个类,一个包,一个模块,都负责一个职责。
例如:
Math.sqrt(x):它只负责求平方根的功能
Math类,只负责和数学计算有关的。
之前有个同学提出过这样的一个问题?
为什么JDK的核心类库中,不把所有的工具方法集中到一个类中?
例如:数学计算,数组计算,集合计算…,这些方法都是静态方法,
为什么不合并到一个类中,我们就不需要记好几个类了。
违反了单一职责的原则。
包:
java.util:各种工具,包括集合
java.net:和网络编程有关的
java.sql:和数据库操作有关的
…
这里主要讨论的是类。
例如:
分层(见课堂笔记的图片)
单一职责:各司其职,好管理
可复用性更好。
3、里氏替换原则:【Liskov Substitution Principle,缩写是LSP】
任何时候都可以用子类型来替换父类型。
换句话说,用子类的类型替换父类的类型时,功能不受影响。
例如:
动物的信息管理系统:涉及到鸟类、燕子类、企鹅类等信息管理。
生物学中,燕子和企鹅都属于鸟类
但是在程序中,这么设计不一定合适。
在程序中还得遵守里氏替换原则。
继承有优点也有缺点?
缺点:无论子类是否需要这个方法,一旦继承,就会继承所有的方法等特征。
要是is-a的关系才能继承。而且要考虑里氏替换原则。
public class TestPrinciple3 {
public static void main(String[] args) {
Swallow s = new Swallow();//燕子
Penguin p = new Penguin();//企鹅
// 我们要观察他们的行为
look(s);
look(p);
}
public static void look(Bird bird){
bird.fly();
bird.walk();
}
}
//鸟的类型
class Bird{
public void walk(){
System.out.println("走");
}
public void fly(){
System.out.println("飞");
}
}
//燕子的类型
class Swallow extends Bird{//燕子
//...
}
//企鹅的类型
class Penguin extends Bird{//企鹅不会飞,所以选择重写fly()
//不应该有这个方法
public void fly(){
throw new RuntimeException("企鹅不会飞");
}
}
public class TestPrinciple3_2 {
public static void main(String[] args) {
Swallow s = new Swallow();//燕子
Penguin p = new Penguin();//企鹅
look(s);
look(p);
}
/* public static void look(Bird bird){
bird.fly();
bird.walk();
}
public static void look(Animal bird){
bird.walk();
}*/
public static void look(Animal a){
a.walk();
if(a instanceof Bird){
Bird b = (Bird) a;
b.fly();
}
}
}
//这个例子中,所有类型都会走,把走这个行为,提取到公共父类中
class Animal{
public void walk(){
System.out.println("走");
}
}
class Bird extends Animal{
public void fly(){
System.out.println("飞");
}
}
class Swallow extends Bird{//燕子
//...
}
class Penguin extends Animal{//企鹅
//...
}
4、依赖倒置原则【Dependence Inversion Principle,缩写是DIP】
要依赖抽象类或接口,而不是依赖具体类型。
主要是指:形参的类型、返回值的类型、变量的引用类型。
即面向抽象编程(抽象类或接口)
例如:
Map接口的API:
Set entrySet()
Collection values()
为什么要这么设计?
例如,(1)上面的Map,在HashMap中,重写Set entrySet()时,可以返回HashSet。
在TreeMap中,重写Set entrySet()是,可以返回TreeSet。
更灵活。
我们调用这个方法,是为了得到所有的(key,value),或者所有的key,要遍历显示他们,
但是至于你内部怎么存我们不关心,对于Set,Collection我们知道统一的遍历方式。
(2)
public void saleMedicine(IPatient patient) {
double money = patient.pay(medicine);
System.out.println(patient.getName() + " 花了 " + money + “块钱买了药:” + medicine.getName());
}
这里形参使用 IPatient接口,可以接受他的各种实现类。
5、接口隔离原则【Interface Segregation Principle,缩写是ISP】
之前依赖倒置原则说,要尽量面向接口、抽象类编程,
那么如何设计接口呢?
例如:
Inter接口,包含:m1,m2,m3,m4四个抽象方法。
B类和D类都是Inter的实现类。
现在有A类要依赖(使用)Inter接口。
A类依赖了Inter接口的m1,m2,m3方法。
C类依赖了Inter接口的m2,m3,m4方法。
通过Inter接口,A类与C类“间接”与B类和D类建立关系。没有发生耦合。
功能没问题,但是违反了最小依赖原则。即接口的隔离不够小。
我们学习过的接口中,是否有这样的设计在里面?
自然比较接口:java.lang.Comparable接口,int compareTo(T t)
定制比较接口:java.util.Comparator接口,int compare(T t1, T 2)
序列号接口:java.io.Serializable接口
Collection集合,会细分为List和Set接口。
因为List系列的都要提供和[index]有个的操作,都是有序的。
因为Set系列的都要保证不可重复。
public class TestPrinciple5 {
public static void main(String[] args) {
A a = new A();
B b = new B();
C c = new C();
D d = new D();
a.test01(b);
a.test02(b);
a.test03(b);
System.out.println("-------------------");
c.test01(d);
c.test02(d);
c.test03(d);
}
}
//这里的四个方法,不适用于所有的场景。
//某些类与我依赖,只依赖1,2,3,某些只依赖2,3,4,或者还有只依赖2,3等
interface Inter{
void m1();
void m2();
void m3();
void m4();
}
class B implements Inter{
@Override
public void m1() {
System.out.println("B类实现了Inter接口的m1方法");
}
@Override
public void m2() {
System.out.println("B类实现了Inter接口的m2方法");
}
@Override
public void m3() {
System.out.println("B类实现了Inter接口的m3方法");
}
@Override
public void m4() {
System.out.println("B类实现了Inter接口的m4方法");
}
}
class D implements Inter{
@Override
public void m1() {
System.out.println("D类实现了Inter接口的m1方法");
}
@Override
public void m2() {
System.out.println("D类实现了Inter接口的m2方法");
}
@Override
public void m3() {
System.out.println("D类实现了Inter接口的m3方法");
}
@Override
public void m4() {
System.out.println("D类实现了Inter接口的m4方法");
}
}
//A类依赖了Inter接口的m1,m2,m3方法。
//因为接口是不能创建对象,最后要给这些方法传入接口的实现类B的对象
class A{
//形参使用接口类型,因为遵循依赖倒置原则
public void test01(Inter inter){
inter.m1();//里面使用了接口的m1方法
}
public void test02(Inter inter){
inter.m2();//里面使用了接口的m2方法
}
public void test03(Inter inter){
inter.m3();//里面使用了接口的m3方法
}
}
//C类依赖了Inter接口的m2,m3,m4方法。
class C{
//形参使用接口类型,因为遵循依赖倒置原则
public void test01(Inter inter){
inter.m2();//里面使用了接口的m2方法
}
public void test02(Inter inter){
inter.m3();//里面使用了接口的m3方法
}
public void test03(Inter inter){
inter.m4();//里面使用了接口的m4方法
}
}
public class TestPrinciple5_2 {
}
//某些类与我依赖,只依赖1,2,3,某些只依赖2,3,4,或者还有只依赖2,3等
interface Inter1{
void m1();
}
interface Inter2{
void m2();
void m3();
}
interface Inter3{
void m4();
}
//B只提供1,2,3
class B implements Inter1,Inter2{
@Override
public void m1(){
System.out.println("B类实现m1");
}
@Override
public void m2(){
System.out.println("B类实现m2");
}
@Override
public void m3(){
System.out.println("B类实现m3");
}
}
//D类只提供2,3,4
class D implements Inter2,Inter3{
@Override
public void m2() {
System.out.println("D类实现m2");
}
@Override
public void m3() {
System.out.println("D类实现m3");
}
@Override
public void m4() {
System.out.println("D类实现m4");
}
}
//A只想依赖Inter接口的1,2,3,当然是通过接口间接依赖的
//传入B类对象就是符合的
class A {
public void test01(Inter1 inter){
inter.m1();
}
public void test02(Inter2 inter){
inter.m2();
}
public void test03(Inter2 inter){
inter.m3();
}
}
//C只想依赖Inter接口的2,3,4,当然是通过接口间接依赖的
//传入D类对象就是符合的
class C{
public void method01(Inter3 inter){
inter.m4();
}
public void method02(Inter2 inter){
inter.m2();
}
public void method03(Inter2 inter){
inter.m3();
}
}
6、迪米特法则【Low of Demeter,缩写是LOD】:
或者也称为最少知识原则(Least Knowledge Principle)
解释:
即一个对象应当对其他对象有尽可能少的了解,目的是保证低耦合。
经典台词:知道的太多,对你没好处。
例如:我们在使用集合时,Collection时,
有一个方法:Iterator iterator()方法。
我们不需要知道ArrayList集合中,到底是用什么类型来实现Iterator迭代器。
public class TestPrinciple6 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("小贾");
list.add("小张");
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
7、组合/聚合复用原则:【Composition/Aggregation Reuse Principle(CARP) 】
尽量使用组合和聚合,少使用继承的关系来达到复用的原则。
继承的优点:代码的复用和扩展
继承的缺点:类的关系过于紧密(无论你是否需要父类中的某些方法等成员,只要继承它了,就会继承所有的方法等成员)
会知道父类的更多的实现细节。
类与类之间的关系:
(1)依赖:只要用到它,在它的代码中出现了,就是依赖
(2)关联关系:有一个成员变量用到了某个类型
持久关系,需要持久化,即存储到数据库中的关系
class Empolyee{
String name;
Computer computer;
}
class Computer{
}
其中Empolyee依赖/关联Computer,但是,可以没有computer对象,
例如某些员工不需要领用电脑设备。
(3)聚合:是更为紧密的关联
class A{
B b;
C c;
}
其中A由B和C组成,A离不开B和C的对象,离开了就不完整了。
但是反过来,B和C可以单独存在。
class Car{
private Engine engine;//引擎
private Tyre[] tyres;//轮胎
}
(4)组合:更进一步的关联
class A{
B b;
C c;
}
其中A由B和C组成,A离不开B和C的对象,离开了就不完整了。
而且B和C离开A也没有意义。
class Window{
private Menu menu;//菜单
private Slider slider;//滑动条
private Panel panel;//工作区
}
(5)继承
类与类之间继承,
实现类与接口之间实现关系也是看成继承。
class A{
}
class B extends A{
}
B继承了所有A的方法,成员变量等,没有继承的构造器也要调用。
在设计代码的时候:
(1)能不依赖的不依赖
(2)能够降低依赖的就降低依赖程度
就如结婚比恋爱时关系紧密了,不自由了。
陌生人之间没关系也就没矛盾。
为什么有的人只敢与喜欢的人做朋友,不能做恋人,因为朋友比恋人关系远一点,
不然一旦分手就不复相见。
三、设计模式
1、23种设计模式
分为三大类:
(1)创建型:单例、原型、工厂方法、抽象工厂、建造者(Builder)
(2)结构型:代理、适配器、桥接、装饰、外观、享元、组合
(3)行为型:模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器
2、今天要学习的
(1)创建型:单例和工厂方法
(2)结构型:代理和装饰
(3)行为型:模板、观察者、迭代器
(一)单例设计模式————创建型
面试高频题
1、单例?
例:实例instance,对象
单例:唯一的对象
单例设计模式:某个类在整个系统中只有唯一的一个对象。
2、如何实现单例?
回忆:
匿名对象:表示对象没有赋值给变量,即没有取名字,不是惟一的 对象。
匿名内部类确实只有唯一的对象。
枚举:枚举是表示某个类型的对象是有限的固定的几个。
第一类:恶汉式单例设计模式(或饿汉式单例设计模式)
————不管是不是需要这个类的对象,都提前创建好这个类的对象
饿:饥不择食,非常着急,
恶:一言不合就开干
(1)JDK1.5之后的枚举形式
例如:
enum EnumSingle{
INSTANCE
}
(2)JDK1.5之前的枚举形式
class Singleton{
private static final Singleton INSTANCE = new Singleton();
private Singleton(){
}
public static Singleton getInstance(){
return INSTANCE;
}
}
(3)
class Single{
public static final Single INSTANCE = new Single();
private Single(){}
}
第二类:懒汉式单例设计模式
懒:不到万不得已,不去做。拖到不得不创建对象。
(1)形式一
class Lazy{
//不着急创建对象
//此时不能加final,因为final必须初始化
private static Lazy instance;//默认值是null
//构造器私有化
private Lazy(){
}
//提供一个方法,来获取这个唯一的对象
public static Lazy getInstance(){
if(instance == null) {//当instance为空时,要竞争锁
synchronized (Lazy.class) {
if (instance == null) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Lazy();
}
}
}
}
}
(2)形式二:
饿汉式是在类初始化的时候创建对象,类初始化由类加载器等完成,他一定是线程安全的。
懒汉式又希望延迟创建对象。
class Only{
private Only(){
}
public static Only getInstance(){
return Inner.INSTANCE;
}
private static class Inner{
static final Only INSTANCE = new Only();
}
}
public class TestSingle {
@Test
public void test01(){
//获取EnumSingle类型的对象
EnumSingle obj1 = EnumSingle.INSTANCE;
EnumSingle obj2 = EnumSingle.INSTANCE;
System.out.println(obj1 == obj2);//true 比较对象的地址,因为只有唯一的一个,获取的对象是同一个
System.out.println(obj1.equals(obj2));//true equals()如果没有重写,和==一样的
}
@Test
public void test02(){
//获取Singleton的对象
Singleton obj1 = Singleton.getInstance();
Singleton obj2 = Singleton.getInstance();
System.out.println(obj1 == obj2);
}
@Test
public void test03(){
//获取Single的唯一对象
Single obj1 = Single.INSTANCE;
Single obj2 = Single.INSTANCE;
System.out.println(obj1 == obj2);
}
@Test
public void test04(){
//获取Lazy的对象
Lazy obj1 = Lazy.getInstance();
Lazy obj2 = Lazy.getInstance();
System.out.println(obj1 == obj2);
}
//现在演示多线程获取Lazy的对象
//这里使用成员变量obj1,obj2的原因,是因为我们要在匿名内部类中使用这个两个变量
Lazy obj1;
Lazy obj2;
@Test
public void test05(){
//获取Lazy的对象
Thread t1 = new Thread(){
public void run(){
obj1 = Lazy.getInstance();
}
};
t1.start();
Thread t2 = new Thread(){
public void run(){
obj2 = Lazy.getInstance();
}
};
t2.start();
//这里要加join,确保两个匿名内部类的线程执行完成之后,再运行以下代码
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("obj1="+ obj1);
System.out.println("obj2="+ obj2);
System.out.println(obj1 == obj2);
}
}
//(1)形式一:枚举形式
enum EnumSingle{
INSTANCE
}
//(2)形式二:类似于JDK1.5之前创建枚举的形式
class Singleton{
//INSTANCE的数据类型是Singleton,因为表示的是Singleton的对象
//INSTANCE大写是因为常量名建议大写
//final:唯一的对象,固定的,不修改的
//static:因为getInstance()方法需要静态,通过类名.进行调用,静态方法只能访问静态成员
//private:表示不对外直接暴露唯一的对象
private static final Singleton INSTANCE = new Singleton();
//构造器私有化之后,我们外面就不能创建它的对象了
private Singleton(){
}
//写一个方法,为外界提供这个唯一的对象INSTANCE
//返回值类型Singleton
public static Singleton getInstance(){
return INSTANCE;
}
public static void method(){
System.out.println("静态方法");
}
}
//形式三:简化第二种形式
class Single{
public static final Single INSTANCE = new Single();
private Single(){}
}
/*
懒汉式
*/
class Lazy{
//不着急创建对象
//此时不能加final,因为final必须初始化
private static Lazy instance;//默认值是null
//构造器私有化
private Lazy(){
}
//提供一个方法,来获取这个唯一的对象
public static Lazy getInstance(){
// return new Lazy();//错误,每次调用都会new
//不合适,有线程安全问题
/* if(instance == null){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Lazy();
}*/
//锁对象:(1)任意类型的对象(2)保证多个线程使用同一个对象
//这里选择Lazy.class
//以下代码没有错误,但是不够完美
/*synchronized (Lazy.class){
if(instance == null){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Lazy();
}
}*/
//优化
if(instance == null) {//当instance为空时,要竞争锁
synchronized (Lazy.class) {
if (instance == null) {
//不能去掉,原因是因为一开始可能有多个线程判断了instance为空
//其中一个线程获得锁,创建对象,等他释放锁之后,其他线程获得锁,仍然要判断
//否则就会出现多个对象。
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Lazy();
}
}
}
return instance;
}
}
/*
在外部类Only初始化时,是不会初始化Inner的,因此此时没有创建Only对象。
当我们调用getInstance方法时,获取Inner.INSTANCE;,才会初始化Inner,才会创建Only对象,
既保证了延迟创建,又保证了线程安全
*/
class Only{
private Only(){
}
public static Only getInstance(){
return Inner.INSTANCE;
}
private static class Inner{
static final Only INSTANCE = new Only();
}
}
(二)工厂设计模式————创建型
1、为什么要使用工厂设计模式?
目标:把对象的创建者(new)与对象的使用者分开。
或者换句话说,把创建对象交给更专业的人(类)来做。
生活中:
最早的社会:自给自足
打猎,用树叶拼凑衣服
在后来:男耕女织
现在:分工更明显
代码中:创建对象这个事情,也不是所有人都合适的。
特别一下复杂的对象,例如:线程池对象,spring容器对象,数据库连接池对象等
这些对象需要读取很多配置文件,甚至有的还要读取系统信息,才能创建好一个对象。
所以,像这类对象,我们不要交给使用者创建,由专门的类(工厂类)来创建,
使用者只要获取即可。
之前总结过,要得到对象:
A:自己new
B:类名.常量对象
C:通过调用方法获取对象
2、工厂设计模式有很多种形式
(1)简单工厂模式
(2)工厂方法设计模式
(3)抽象工厂模式(今天不讲,非常复杂,适用于创建系列对象用的)
简单工厂模式:
例子:
(1)有一个接口:产品的标准
(2)有很多产品的具体类型
(3)声明一个工厂类
版本1:
public static Car createCar(String type){
switch (type){
case “宝马”:
return new BMW();
case “奔驰”:
return new Benz();
case “凤凰”:
return new FengHuang();
default:
return null;
}
}
当我增加产品,例如:红旗车,就要修改createCar()方法,
就违反了“开闭原则”,对修改关闭,对扩展开放。
形式二:
class SimpleFactory2{
public static Car createBMWCar(){
return new BMW();
}
public static Car createBenzCar(){
return new Benz();
}
public static Car createFengHuangCar(){
return new FengHuang();
}
}
增加新产品,例如:红旗车,再添加一个方法即可。对原来的代码不需要修改。
但是问题在于,产品如果很多,就会导致这个方法很多。
而且严格来说,也是修改了类了。
形式三:
使用反射,见下面的SimpleFactory3。
增加新产品,例如:红旗车,不需要修改代码,
但是如果要为产品的成员变量初始化的话,比较麻烦。
public class TestSimpleFactory {
@Test
public void test01(){
//没有工厂的时候,要得到我们的具体的车的产品对象
//TestSimpleFactory这里代表类的使用者,
//它与BMW这里依赖了
Car bm = new BMW();
bm.run();
}
@Test
public void test02(){
//使用工厂
//现在使用者TestSimpleFactory与具体的BMW类型解耦合了
//至于在工厂内部如何造成复杂的宝马车,这里不需要知道
Car bm = SimpleFatory.createCar("宝马");
bm.run();
}
@Test
public void test03(){
Car bm = SimpleFactory2.createBMWCar();
bm.run();
}
@Test
public void test04(){
Car bm = SimpleFactory3.createCar("com.atguigu.factory.test02.BMW");
bm.run();
}
}
//产品的标准
interface Car{
void run();
}
//产品的具体类型
class BMW implements Car{
@Override
public void run() {
System.out.println("坐在宝马车里哭");
}
}
class Benz implements Car{
@Override
public void run() {
System.out.println("坐在引擎盖上哭");
}
}
class FengHuang implements Car{
@Override
public void run() {
System.out.println("坐在自行车上哭");
}
}
class SimpleFatory{
//有一个方法,可以提供车的产品对象
//重命名快捷键:shift+F6
public static Car createCar(String type){
switch (type){
case "宝马":
return new BMW();
case "奔驰":
return new Benz();
case "凤凰":
return new FengHuang();
default:
return null;
}
}
}
//提出解决方案之一:
class SimpleFactory2{
public static Car createBMWCar(){
return new BMW();
}
public static Car createBenzCar(){
return new Benz();
}
public static Car createFengHuangCar(){
return new FengHuang();
}
}
//提出解决方案之二:
class SimpleFactory3{
public static Car createCar(String type){
try {
//传类的具体的名称
//使用反射
//要求产品类型必须有“无参构造”
Class clazz = Class.forName(type);
return (Car)clazz.newInstance();
//这里如果要为对象的成员变量赋值的话,这种方式比较麻烦
//因为每一种类的成员变量不同
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
return null;
}
}
工厂方法设计模式
如何实现?
(1)产品的标准接口
(2)具体的产品类,接口的实现类
(3)工厂的接口
(4)具体的工厂类
如果现在增加新产品类,例如:红旗车,不需要修改原来的类,
只要增加新的工厂类即可。
遵循了“开闭原则”,也遵循了“单一职责”原则等
public class TestFactoryMethod {
@Test
public void test01(){
//通过宝马工厂获取宝马车对象时
CarFactory cf = new BMWFactory();
Car bm = cf.createCar();
bm.run();
}
}
interface Car{
void run();
}
//产品的具体类型
class BMW implements Car{
@Override
public void run() {
System.out.println("坐在宝马车里哭");
}
}
class Benz implements Car{
@Override
public void run() {
System.out.println("坐在引擎盖上哭");
}
}
//工厂的接口
//CarFactory接口的实现类,都是用来生产Car的实现类的对象的
interface CarFactory{
Car createCar();
}
//具体的工厂类
class BMWFactory implements CarFactory{
//这里返回值类型BMW<Car,遵循重写的要求
@Override
public BMW createCar() {
return new BMW();
}
}
class BenzFactory implements CarFactory{
@Override
public Benz createCar() {
return new Benz();
}
}
(三)、代理设计模式
1、为什么要使用代理模式?
当访问对象(使用者)与最终的目标对象之间不能或不适合直接引用,
我们可以在它们中间加入代理。
生活:
我们买、租房子的时候,
我们和业主不方便直接见面,或者我根本就不清楚业主是谁。
那么我会找中介。
我们不适用挨家挨户的问,即麻烦,又不安全。
业主会更相信中介公司,他们会要卖或租的房子信息告知它们,
我们从中介获取信息。
例如:
线程。
我们使用者要启动线程,对线程进行操作,我们线程是要通过操作系统
底层的代码进行启动的。
大多数程序员是没有能力调用底层的代码。这么做也不安全。
Java就提供了一个Tread这个类作为“代理”,帮我们启动和操作线程。
所有要启动线程都要用到Thread类的start()。
不管是继承Thread类还是实现Runnable接口的方式。
2、如何实现代理?
代理模式有两种形式:静态代理模式和动态代理模式
不管哪一种代理,都有三个要素:
(1)被代理者
(2)代理者
(3)代理主题(接口)
静态代理模式
(1)被代理者
(2)代理者
(3)代理主题(接口)
如果被代理类很多,接口不同,
但是代理工作内容相同,例如:都是记录日志,统计时间等,
这个编写多个代理类太麻烦了,冗余的代码太多。
但是如果被代理类不同,代理工作也不同,
那么单独编写代理类是合适的。
public class TestStaticProxy {
@Test
public void test01(){
//创建被代理者对象
UserDAO ud = new UserDAO();
//创建代理类对象
UserDAOProxy udp = new UserDAOProxy(ud);
//调用代理的add方法
udp.add();
}
}
//主题接口
//进行数据库操作的公共接口
interface DAO{
void add();
// void delete();
// void update();
// void select();
}
interface IService{
//...
}
//被代理者
class UserDAO implements DAO{
@Override
public void add() {
System.out.println("添加一个用户");
}
}
//被代理者2
class GoodsDAO implements DAO{
@Override
public void add() {
System.out.println("添加一个商品");
}
}
//代理者
/*
代理工作:
项目经理说,给所有DAO的实现类的所有方法,
提供一个运行时间的测试和日子记录。
计算每一个方法开始运行和结束运行的时间,计算时间差,
并且打印xx方法开始运行
这个功能不是一直都要的,而且是开发环境需要,上线时不要。
不适合在上面的被代理类中直接加这个代码
*/
class UserDAOProxy implements DAO{
//具体的被代理者对象
private UserDAO ud;
public UserDAOProxy(UserDAO ud){
this.ud = ud;
}
@Override
public void add() {
System.out.println("UserDAO的add方法开始执行");
long start = System.currentTimeMillis();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//运行实际的具体的业务代码
//调用UserDAO的add方法
ud.add();
long end = System.currentTimeMillis();
System.out.println("运行时间:"+(end-start)+"毫秒");
}
}
class GoodsDAOProxy implements DAO{
private GoodsDAO gd;
public GoodsDAOProxy(GoodsDAO gd){
this.gd = gd;
}
@Override
public void add() {
System.out.println("GoodsDAO的add方法开始执行");
long start = System.currentTimeMillis();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//运行实际的具体的业务代码
//调用GoodsDAO的add方法
gd.add();
long end = System.currentTimeMillis();
System.out.println("运行时间:"+(end-start)+"毫秒");
}
}
动态代理模式
(1)被代理者
(2)代理者
(3)代理主题(接口)
(4)代理工作处理器:要求必须实现InvocationHandler接口,
实现
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
代理工作内容是相同的。
例如:都是记录日志,统计时间
(5)在使用代理类的位置“动态的”创建代理类及其他的对象
代理类是动态编译生成的,而不是事先写好的。
要求:使用java.lang.reflect.Proxy,
Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。
A:static Class<?> getProxyClass(ClassLoader loader, Class<?>… interfaces)
先获取代理类的Class对象,然后再创建对象
B:static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
直接获取代理类的对象,至于这个类在内存中待着就好了,我不获取他
public class TestDynamicProxy {
@Test
public void test01(){
//创建被代理者对象
UserDAO ud = new UserDAO();
//获取被代理者UserDAO的类加载器对象
Class clazz = ud.getClass();
ClassLoader loader = clazz.getClassLoader();
//获取被代理者UserDAO实现的所有接口
Class<?>[] interfaces = clazz.getInterfaces();
//创建代理工作处理器对象
InvocationHandler h = new MyHandler(ud);//ud是被代理者
//动态创建代理类对象
//是UserDAO的代理类对像
//newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
//参数一:ClassLoader loader,类加载器,我们要加载一个类或动态生成一个类的Class都需要类加载器
// 这里最好和被代理者使用同一个类加载器最好。
//参数二:Class<?>[] interfaces,被代理者实现的所有的接口们
//参数三:InvocationHandler h,代理工作处理器对象
// 代理者替被代理者完成xx工作
//方法调用得到的是代理类的对象
Object proxy = Proxy.newProxyInstance(loader,interfaces,h);
//向下转型,为了调用add等方法
DAO dao = (DAO) proxy;
dao.add();
Object user = dao.select();
System.out.println("查询结果:" + user);
}
@Test
public void test02(){
//创建被代理者对象
GoodsDAO ud = new GoodsDAO();
//获取被代理者GoodsDAO的类加载器对象
Class clazz = ud.getClass();
ClassLoader loader = clazz.getClassLoader();
//获取被代理者GoodsDAO实现的所有接口
Class<?>[] interfaces = clazz.getInterfaces();
//创建代理工作处理器对象
InvocationHandler h = new MyHandler(ud);//ud是被代理者
//动态创建代理类对象
//是UserDAO的代理类对像
//newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
//参数一:ClassLoader loader,类加载器,我们要加载一个类或动态生成一个类的Class都需要类加载器
// 这里最好和被代理者使用同一个类加载器最好。
//参数二:Class<?>[] interfaces,被代理者实现的所有的接口们
//参数三:InvocationHandler h,代理工作处理器对象
// 代理者替被代理者完成xx工作
//方法调用得到的是代理类的对象
Object proxy = Proxy.newProxyInstance(loader,interfaces,h);
//向下转型,为了调用add等方法
DAO dao = (DAO) proxy;
dao.add();
Object user = dao.select();
System.out.println("查询结果:" + user);
}
@Test
public void test03(){
Other other = new Other();
Class clazz = other.getClass();
IService o = (IService) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), new MyHandler(other));
o.fang();
o.fun();
}
}
//主题接口
//进行数据库操作的公共接口
interface DAO{
void add();
// void delete();
// void update();
Object select();
}
interface IService{
void fang();
void fun();
}
//被代理者
class UserDAO implements DAO{
@Override
public void add() {
System.out.println("添加一个用户");
}
@Override
public Object select() {
return "用户对象";
}
}
//被代理者2
class GoodsDAO implements DAO{
@Override
public void add() {
System.out.println("添加一个商品");
}
@Override
public Object select() {
return "商品对象";
}
}
//代理工作处理器
class MyHandler implements InvocationHandler{
//被代理者,可能是GoodsDAO的对象,也可能是UserDAO的对象,也可能是其他被代理者对象
//所以这里使用Object类型接收。
private Object target;
public MyHandler(Object target){
this.target = target;
}
/**
* 代理工作处理器必须重写的方法。这个方法不是程序员调用的,由被代理者工作被启动时自动调用的。
* 我们需要在这个方法中,编写代理者要替被代理者完成xx工作内容
* @param proxy Object:代理者对象
* @param method Method:代理者要替被代理代理的方法,例如:上面的add方法等
* @param args Object[]:代理者要替被代理代理的方法的实参列表,可能有的方法不需要
* @return Object:代理者要替被代理代理的方法的返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(target.getClass()+"的"+method.getName()+"方法开始执行");
long start = System.currentTimeMillis();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//运行实际的具体的业务代码
//调用被代理者target的method方法
//使用反射的知识点,这里表示target对象的method方法被调用了
//method方法代表上面被代理的add,select,fun等之一
Object returnValue = method.invoke(target,args);
long end = System.currentTimeMillis();
System.out.println("运行时间:"+(end-start)+"毫秒");
return returnValue;
}
}
//其他的被代理者
class Other implements IService{
@Override
public void fang() {
System.out.println("另一个接口的fang");
}
@Override
public void fun() {
System.out.println("另一个接口的fun");
}
}