设计模式
设计模式(GOF,Gound Of Four)一共有23中,这些模式是为了让我们的代码更有效率而被开发的。
1、单例模式
单例模式也就是虚拟机只有一个实例对象,好处是可以直接访问,不需要实例化该类的对象。。
代码演示
1.1饿汉式
public class Demo1 {
//创建一个私有静态常量对象,这样子这个对象就不可以被更改,也不可以直接被访问
private static final Demo1 d=new Demo1();
//返回一个对象,这样可以访问到这个私有对象
public static Demo1 getD(){
return d;
}
}
public class Demo1Test {
public static void main(String[] args) {
//获取两个对象
Demo1 d = Demo1.getD();
Demo1 d1 = Demo1.getD();
//输出对象的地址,你会发现两个地址相同,因此它们是同一个对象
System.out.println(d);
System.out.println(d1);
}
}
结果
org.ji21.code.Demo1@4554617c
org.ji21.code.Demo1@4554617c
1.2、懒汉式,它与饿汉式不同在于它不马上创建对象,等到调用时,在创建对象。
public class Demo2 {
//声明对象
private static Demo2 D;
//获得访问对象,添加synchronized关键字,这是因为多线程时,线程一同获取该方法,
//刚好一个线程在判断是否被实例化,还没有来得及执行实例化,执行权被另一个线程抢到,
//它也进行判断,这时因为还没有实例化,因此也会通过if判断,这时就会创建两个对象,
//这就违反了单例的初衷了
public static synchronized Demo2 getD(){
//因为只创建一个实例,因此判该对象是否已经被实例化,如果没有就实例化,要保证只有一个实例对象
if(D==null){
D=new Demo2();
}
return D;
}
}
public class Demo2Test {
public static void main(String[] args) {
Demo2 d = Demo2.getD();
Demo2 d1 = Demo2.getD();
System.out.println(d);
System.out.println(d1);
}
}
结果
org.ji21.code.Demo2@4554617c
org.ji21.code.Demo2@4554617c
1.3枚举的方式(也是饿汉式)
public enum Demo3 {
singel;
}
public class Demo3Test {
public static void main(String[] args) {
Demo3 demo = Demo3.singel;
Demo3 demo1 = Demo3.singel;
System.out.println(demo==demo1);
}
}
结果
true
1.4懒汉式更好的实现是用内部类私有化
public class Demo4 {
//内部类私有式外部不能访问他,在里面创建静态对象,当类加载时,静态的成员也被加载,
//这时就创建了一个Demo4对象,也就是说它只加载了一次,因此下面得到的对象d也就只有一个
private static class newInstance{
static Demo4 d=new Demo4();
}
//返回一个Demo4对象
public static Demo4 getd(){
return newInstance.d;
}
}
public class Demo4Test {
public static void main(String[] args) {
Demo4 getd = Demo4.getd();
Demo4 getd1 = Demo4.getd();
System.out.println(getd==getd1);
}
}
结果
true
2、享元模式(flyweight)
这个模式是用来重用已有的对象的,如前面说写过的Integer对象,它就是一个享元模式下的对象,它在-128~127之间都是在用同一个空间,只有超出了范围,它才会创建新的空间,封装的基本数据类型都有这个特点。
代码演示
public class Demo5Test {
public static void main(String[] args) {
//Integer它在-128~127之间是不会new新空间的,因此第一个输出是true
Integer x1=Integer.valueOf(100);
Integer y1=Integer.valueOf(100);
System.out.println(x1==y1);
//超过范围这里是false
Integer x2=Integer.valueOf(200);
Integer y2=Integer.valueOf(200);
System.out.println(x2==y2);
}
}
结果
true
false
3、原形模式(prototype)
这个模式是根据已有的对象,来创建新的对象的,也就是克隆。
代码演示
1、克隆姓名、年龄和日期
import java.util.Date;
//克隆需要继承克隆接口,重写里面的方法
public class Demo5 implements Cloneable{
private String name;
private int age;
private Date date;
//重写toString方法,方便看输出
@Override
public String toString() {
return "Demo5{" +
"name='" + name + '\'' +
", age=" + age +
", date=" + date +
'}';
}
public Demo5(String name,int age,Date date) {
this.name=name;
this.age=age;
this.date = date;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public Date getDate() {
return date;
}
//重写克隆方法
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Demo5Test {
public static void main(String[] args) {
Demo5 p1 = new Demo5("jack",33,new Date());
System.out.println(p1.toString());
Demo5 p2= null;
try {
//上面的重写的克隆方法返回的是Object类型,因为要给的是p2赋值,因此要转型为Demo5
p2 = (Demo5) p1.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
System.out.println(p2.toString()) ;
}
}
这样就克隆成功了(浅克隆),但是如果你想要改变其中某个克隆值得话,会出现一些问题,如上述的日期类,它会一起改变,因为它是引用数据类型,传过去的是地址,因此改变时,会一起改变,反之基本数据类型就不会,要想解决这个问题就要用到序列化流和反序列化流了,因为我序列化和反序列化流用来操作对象的,这样克隆到的数据会立即写入,不会像之前那样只复制链接,而值没有立即更改的情况了,这也就是深克隆。
2、深克隆
import java.io.*;
import java.util.Date;
//序列化需要实现Serializable接口
public class Demo7 implements Cloneable, Serializable{
private String name;
private int age;
private Date date;
@Override
public String toString() {
return "Demo7{" +
"name='" + name + '\'' +
", age=" + age +
", date=" + date +
'}';
}
public Demo7(String name, int age, Date date) {
this.name = name;
this.age = age;
this.date = date;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public Date getDate() {
return date;
}
@Override
protected Object clone() throws CloneNotSupportedException {
//读入字节流
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try {
//把当前对象写入序列化流
new ObjectOutputStream(byteArrayOutputStream).writeObject(this);
//把字节流变为字节数组
byte[] bytes = byteArrayOutputStream.toByteArray();
//把字节数组入读字节流中,也就是读其中的数据
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
//用反序列流读取字节流读入的数据
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
return objectInputStream.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
import java.util.Date;
public class Demo7Test {
public static void main(String[] args) {
Demo7 p1 = new Demo7("jack", 40, new Date());
System.out.println(p1.toString());
try {
Demo7 p2=(Demo7) p1.clone();
//获取日期对象,并把其中的小时改为8
p2.getDate().setHours(8);
System.out.println(p2.toString());
} catch (CloneNotSupportedException e) {
//e.printStackTrace();
}
}
}
结果
Demo7{name='jack', age=40, date=Thu Nov 29 10:56:17 CST 2018}
Demo7{name='jack', age=40, date=Thu Nov 29 08:56:17 CST 2018}
4、策略模式(Strategy)
这个模式是用来策划java中的集合或数组的排序算法,对于规模小的使用的是插入排序,基本数据类型用的是双基点快速排序,Arrays.sort()和Collections.sort(),它们底层实现都是TimSort实现的,但是排序算法并不是固定的,它会根据程序员的需求而改变,不过你需要自己去实现排序算法,这个时候就用到了比较器(Comparator)。
代码演示
public class Demo6Test {
public static void main(String[] args) {
int[] a={4,3,9,7,6};
System.out.println("排序前");
//直接用Arrays的toString方法可以直接输出数组
System.out.println(Arrays.toString(a));
//Arrays.sort()的参数只能是数组,基本数据类型Object类型的数组
Arrays.sort(a);
System.out.println("排序后");
System.out.println(Arrays.toString(a));
List<Integer> list = new ArrayList<>();
list.add(5);
list.add(3);
list.add(9);
list.add(4);
list.add(6);
list.add(7);
list.add(8);
System.out.println("排序前");
ListIterator<Integer> iterator = list.listIterator();
while (iterator.hasNext()){
Integer next = iterator.next();
System.out.print(next+" ");
}
System.out.println("");
//Collections的sort方法里填的参数只能是List集合
Collections.sort(list);
System.out.println("排序后");
ListIterator<Integer> iterator2 = list.listIterator();
while (iterator2.hasNext()){
Integer next = iterator2.next();
System.out.print(next+" ");
}
}
}
结果
排序前
[4, 3, 9, 7, 6]
排序后
[3, 4, 6, 7, 9]
排序前
5 3 9 4 6 7 8
排序后
3 4 5 6 7 8 9
5、迭代器模式(iterator)
这种模式不需要对集合内部构造进行了解,直接遍历元素,这个迭代器经常在Collection集合中用到,上面那个排序的例子就用到了迭代器。
6、建造器模式
建造器模式就入之前说过的stringBuffe类,里的append它就用到了构造器,可以用逗号添加元素,而且构造器可以选择你想填的内容,不写的内容默认空值,也可以无序调用,这就方便了我们想给某个属性赋值。
代码演示
public class Demo6 {
private String name;
private int age;
private String sex;
//重写toString方法
@Override
public String toString() {
return "Demo6{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
//私有构造方法使外界不能直接访问它,这样就不需要实例化了
private Demo6(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
//建造构造器,其实构造器就是一个静态的内部类,当外部类加载时,它也被加载,
//到时就直接用外部类调用它,那也就可以调用它里面的方法了
public static class Bulider {
private String name;
private int age;
private String sex;
//里面的方法返回的是该类,这样调用完一个方法,返回该类,又可以继续调用方法(用类名.方法名的方法)
public Bulider Name(String name) {
this.name=name;
return this;
}
public Bulider Age(int age) {
this.age=age;
return this;
}
public Bulider Sex(String sex) {
this.sex=sex;
return this;
}
//最后在该类中返回一个外部类的实例,这样外面的私有成员变量,就被赋值了,重写的toString方法获得这些值,就可以按格式输出了
public Demo6 person() {
return new Demo6(this.name,this.age,this.sex);
}
}
}
结果
Demo6{name='null', age=50, sex='male'}
Demo6{name='lucy', age=20, sex='null'}
7、工厂模式
这种设计模式,它提供了一种创建对象的最佳方式,在模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
代码演示
工厂中制造篮球、娃娃、笔,客户从中拿到相应的对象
创建一个接口
public interface Production {
public abstract void production();
}
实现接口,从工厂中拿到制造好的铅笔
public class Pen implements Production{
@Override
public void production() {
System.out.println("从工厂中拿到铅笔了");
}
}
实现接口,从工厂中拿到制造好的篮球
public class Basketball implements Production{
@Override
public void production() {
System.out.println("我从工厂中拿到了篮球");
}
}
实现接口,从工厂中拿到制造好的娃娃
public class Doll implements Production{
@Override
public void production() {
System.out.println("我从工厂中拿到了娃娃");
}
}
通过输入的字符串来判断应该创建哪一个对象
public class Factory {
public Production getPro(String prodection){
//判断这个方法的参数是否是那几个产品,如果是就返回相应的产品,否则就返回null,equalsIgnoreCase表示忽略字符串大小写
if(prodection==null){
return null;
}else if(prodection.equalsIgnoreCase("doll")){
return new Doll();
}else if(prodection.equalsIgnoreCase("basketball")){
return new Basketball();
}else if(prodection.equalsIgnoreCase("pen")){
return new Pen();
}
System.out.println("工厂中没有这个产品");
return null;
}
}
测试类
public class FactoryTest {
public static void main(String[] args) {
//创建工厂对象,调用方法,方法的返回值是相应的对象,那就会调用相应对象的方法,从这里可以看出,我们并不是直接把相应的对象new出来而是通过工厂返回的对象来调用对象的方法的
Factory factory = new Factory();
factory.getPro("Doll").production();
factory.getPro("basketball").production();
factory.getPro("pen").production();
}
}
结果
我从工厂中拿到了娃娃
我从工厂中拿到了篮球
从工厂中拿到铅笔了