设计模式:
设计模式:其实就是根据代码总结出来的一套经验。一套思路,大家套(模板)
课程目标:
-
了解设计模式的原则
分层模式:Dao MVC 面向接口编程——设计模式没有具体的代码实现,只是一个指导思想。
23中设计模式——大话设计模式
工厂模式 适配器模式 策略模式 观察者模式…
常用设计模式——
生产者消费者 单例模式(Servlet单例) 代理模式
第一节:设计模式遵循的原则:
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。
使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
总原则:开闭原则
开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,而是要扩展原有代码,实现一个热插拔的效果。
对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对类进行任何修改。
这样的设计,能够面对需求改变却可以保持相对稳定,从而使系统在第一个版本以后不断推出新的版本;面对需求,对程序的改动是通过增加新的代码进行的,而不是更改现有的代码;
所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类等
1、单一职责原则
定义:不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。
问题由来:类T负责两个不同的职责:职责P1,职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障。
解决方案:遵循单一职责原则。分别建立两个类T1、T2,使T1完成职责P1功能,T2完成职责P2功能。这样,当修改类T1时,不会使职责P2发生故障风险;同理,当修改T2时,也不会使职责P1发生故障风险。
2、里氏替换原则
里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
里氏替换原则中,子类对父类的方法尽量不要重写和重载。因为父类代表了定义好的结构,通过这个规范的接口与外界交互,子类不应该随便破坏它。
3、依赖倒置原则
定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。
问题由来:类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子 *** 作;假如修改类A,会给程序带来不必要的风险。
解决方案:将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。
4、接口隔离原则
这个原则的意思是:每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。
接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性是不挣的事实,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度
5、迪米特法则(最少知道原则)
高内聚低耦合
就是说:一个类对自己依赖的类知道的越少越好。也就是说无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过public方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。
最少知道原则的另一个表达方式是:只与直接的朋友通信。类之间只要有耦合关系,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。我们称出现为成员变量、方法参数、方法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。我们要求陌生的类不要作为局部变量出现在类中。
6、合成复用原则
尽量使用合成/聚合.避免继承.在新类中应该尽量使用关联关系采用现有的对象,使之成为新对象的一部分.达到现有功能复用的目的.
通过合成聚合的原则可以降低类于类之间的依赖关系.被依赖的类的修改对其他类的影响相对小一些.
合成/聚合原则是动态的.可以自由选择使用现有类的那些方法.而继承是静态的,失去了灵活性.如果父类改变有可能会影响子类的修改,同时破坏了父类的封装性,父类将会暴露不相关的方法给子类.
第二节:单例模式(单子)
单例模式(Singleton Pattern)
类型:创建性模式
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
使用场景:在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。
单例模式的三要素:
Ø 私有的构造方法;
Ø 指向自己实例的私有静态引用;—私有的静态成员变量:数据类型是自己
Ø 以自己实例为返回值的静态的公有方法。----
单例模式根据实例化对象时机的不同,有两种经典的实现:一种是 饿汉式单例(立即加载),一种是 懒汉式单例(延迟加载)。
饿汉式单例(线程安全)
饿汉式单例在单例类被加载时候,就实例化一个对象并交给自己的引用;
Ø 是否 Lazy 初始化:否
Ø 是否多线程安全:是
Ø 描述:这种方式比较常用,但容易产生垃圾对象。
Ø 优点:没有加锁,执行效率会提高。
Ø 缺点:类加载时就初始化,浪费内存。
/**
* 单例:
* 饿汉式单例
* 效率挺高 线程安全 就是有点浪费:需要或者不需要都会加载,第一次加载类的时候就会创建对象。
*/
public class Sington01 {
//static特性:静态,属于类,共享的。静态:类加载的时候就会执行。实例变量是在new的时候开辟
//static:类直接调用,而且共享。
//所以调用或者不调用getInstance,只要加载了这个类就会被创建。
private static Sington01 sington01=new Sington01();
private Sington01(){
System.out.println("Sington01被实例化");
}
public static Sington01 getInstance(){
return sington01;
}
}
调用:
public class SingleType1 {
public static void main(String[] args) {
Sington01 s1 = Sington01.getInstance();
Sington01 s2 = Sington01getInstance();
System.out.println(s1 == s2);
}
}
示例二:懒汉式单例
而懒汉式单例只有在真正使用的时候才会实例化一个对象并交给自己的引用
Ø 是否 Lazy 初始化:是
Ø 是否多线程安全:否
Ø 实现难度:易
Ø 描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
必须加锁 synchronized 才能保证单例,但加锁会影响效率
/**
* 单例:
* 懒汉式单例——懒加载,什么时候需要什么时候new
线程不安全,有可能被new多次
*/
public class Sington02 {
private static Sington02 sington02;
private Sington02(){
System.out.println("Sington01被实例化");
}
public static Sington02 getInstance(){
if(sington02==null) {
sington02 = new Sington02();
}
return sington02;
}
}
测试:
public class TManyTh implements Runnable {
@Override
public void run() {
//获取单例
Sington02 s=Sington02.getInstance();
System.out.println(s);
}
public static void main(String[] args) {
//
for (int i = 0; i <30 ; i++) {
TManyTh t=new TManyTh();
Thread thread=new Thread(t);
thread.start();
}
}
}
我们创建多个线程,发现并不能实现实例对象的唯一性;
这是因为如果两个线程都执行到 if(null == instance){
的时候,如果其中有一个由于没有抢到时间片则不能向下执行,
另一个线程抢到则检测没有对象,则又创建一个实例对象;
优点:实现了lazy loading 但是线程不安全,只能用在单线程中,不可用;
加锁:
/**
* 单例:
* 懒汉式单例——懒加载,什么时候需要什么时候new
* 加锁:让线程安全
*/
/**
* singtoin需要去内存取值:
* 如果没有volatile:有可能出现读取的内容不是最新
* volatile:保证读取到最新的,但是保证原子性(代码不可分割——锁)
*/
public class Singleton {
private static volatile Singleton s;
private Singleton(){};
public static Singleton getInstance() {
if(s == null) {
synchronized (Singleton.class) {
if(s == null) {
s = new Singleton();
}
}
}
return s;
}
}
为什么是双重校验锁实现单例模式呢?
第一次校验:也就是第一个if(singleton==null),这个是为了代码提高代码执行效率,由于单例模式只要一次创建实例即可,所以当创建了一个实例之后,再次调用getInstance方法就不必要进入同步代码块,不用竞争锁。直接返回前面创建的实例即可。
第二次校验:也就是第二个if(singletonnull),这个校验是防止二次创建实例,假如有一种情况,当singleton还未被创建时,线程t1调用getInstance方法,由于第一次判断singletonnull,此时线程t1准备继续执行,但是由于资源被线程t2抢占了,此时t2页调用getInstance方法,同样的,由于singleton并没有实例化,t2同样可以通过第一个if,然后继续往下执行,同步代码块,第二个if也通过,然后t2线程创建了一个实例singleton。此时t2线程完成任务,资源又回到t1线程,t1此时也进入同步代码块,如果没有这个第二个if,那么,t1就也会创建一个singleton实例,那么,就会出现创建多个实例的情况,但是加上第二个if,就可以完全避免这个多线程导致多次创建实例的问题。
所以说:两次校验都必不可少。
优点:即实现了懒加载,又保证了线程安全;
示例三:懒汉式单例
为了保证效率,可以使用静态内部类的形式实现
/**
* 静态内部类:
* 懒汉单例
*/
public class Sington4 {
private Sington4(){}
static class SingtonTest{
private static Sington4 sington4=new Sington4();
}
public static Sington4 getInstance(){
return SingtonTest.sington4;
}
}
小结:
以上单例模式用法各有优劣,
懒汉模式的优点便是在代码中没有使用的情况下,不会去加载单例类的资源不会造成资源的浪费。缺点也很明显,加锁同步会带来程序运行效率的损失。
饿汉模式的优缺点恰好与懒汉模式相反,如果明确知道单例对象在程序代码中用的很频繁,就可以考虑使用饿汉模式了。
第二节:工厂模式
工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
应用实例: 1、您需要一辆汽车,可以直接从工厂里面提货,而不用去管这辆汽车是怎么做出来的,以及这个汽车里面的具体实现。 2、Hibernate 换数据库只需换方言和驱动就可以。3、日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。
简单工厂(普通工厂)
public class CarFactory{
public static Car getCar(String name){
if(name.equals("mini")){
return new MiniCar();
}else if(name.equals("pk")){
return new PKCar();
}else if(name.equals("suv")){
return new SUV();
}else if(name.equals("mvp")){
return new MVPCar();
}else{
return null;
}
}
}
/**
* 工厂:月饼加工厂 汽车加工厂.......
* 月饼:不用自己造 工厂造
* 车:不用自己造 工厂造
* 工厂模式:
* 正常需要一个对象时候都是自己new
* 工厂模式:我自己需要一个对象,我不new ,工厂负责new好对象给我
*/
public class MainTest {
public static void main(String[] args) {
//MiniCar miniCar=new MiniCar();
Car car=CarFactory.getCar("mini");
}
}
多个工厂方法模式
该模式是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象
public Car getMiniCar(){
return new MiniCar();
}
public Car getMVPCar(){
return new MVPCar();
}
public Car getPKCar(){
return new PKCar();
}
CarFactory carFactory=new CarFactory();
Car car1=carFactory.getMiniCar();
静态工厂方法模式
静态工厂方法模式,将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可
public static Car getMiniCar(){
return new MiniCar();
}
public static Car getMVPCar(){
return new MVPCar();
}
public static Car getPKCar(){
return new PKCar();
}
抽象工厂模式:
第三节:原型模式
思考:解决克隆狗狗的问题:
有一只狗狗,一岁了,黑白色,名字叫做tom,朋友想克隆十只狗狗怎么做?
传统案例:
概念
原型模式,是用原型实例指定创建对象的种类,
并且通过拷贝这些原型创建新对象,
其实就是从一个对象再创建另外一个可定制的对象,
而且不需要知道任何创建的细节(实际上就是copy)。
使用原型模型解决格隆狗狗的问题:
package design.prototype;
public class Dog implements Cloneable{
private String name;
private int age;
private String color;
private String Address;
private Dog friend;
public Dog getFriend() {
return friend;
}
public Dog(String name, int age, String color, String address, Dog friend) {
this.name = name;
this.age = age;
this.color = color;
Address = address;
this.friend = friend;
}
public void setFriend(Dog friend) {
this.friend = friend;
}
public Dog(String name, int age, String color, String address) {
this.name = name;
this.age = age;
this.color = color;
Address = address;
}
public String getAddress() {
return Address;
}
public void setAddress(String address) {
Address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
", color='" + color + '\'' +
", Address='" + Address + '\'' +
'}';
}
public Dog(String name, int age, String color) {
this.name = name;
this.age = age;
this.color = color;
}
public Dog() {
}
}
测试代码
package design.prototype;
public class Test {
public static void main(String[] args) throws Exception {
Dog dog = new Dog("tom",1,"黑白色","德国牧羊犬");
Dog friend = new Dog("herry",2,"白色");
dog.setFriend(friend);
for (int i = 0; i <10 ; i++) {
Dog d = (Dog)dog.clone();
System.out.println(d+"--->"+d.getFriend()+"-->"+d.getFriend().hashCode());
}
}
}
只能实现值的复制则是浅拷贝;
深度拷贝:
package design.prototype;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class ObjClone {
public static <T> T cloneObj(T obj){
T retVal =null;
try {
// 将对象写入流中
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
// 从流中读出对象
ByteArrayInputStream bais =
new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
retVal = (T) ois.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return retVal;
}
}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)