展示网站项目
给客户A做了一个产品展示网站,客户A的朋友感觉效果不错,但是需求不同。==》有人希望以博客的形式,或者微信公众号的形式发布
传统方案
存在的问题:
因为需要的网站结构相似度高,并且都不属于高访问量的项目,若分成多个虚拟空间处理,则相当于一个相同网站的实例对象被创建了很多,造成服务器的资源浪费。
解决思路:整个到一个网站,共享相关的代码和数据。
享元模式
摒弃了在每个对象中保存所有数据的方式, 通过共享多个对象所共有的相同状态, 让你能在有限的内存容量中载入更多对象。运用共享技术有效地支持大量细粒度的对象。
常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是已创建的连接对象。而我们需要直接调用即可,避免重新创建。
享元模式能够解决重复对象的内存浪费的问题,当系统中有大量相似对象,需要缓冲池时。不需总是创建新对象,可以从缓冲池里拿。这样可以降低系统内存,同时提高效率。
享元模式经典的应用场景就是池技术了,String常量池、数据库连接池、缓冲池等等都是享元模式的应用 ,享元模式是池技术的重要实现方式。
享元模式只是一种优化。 在应用该模式之前, 你要确定程序中存在与大量类似对象同时占用内存相关的内存消耗问题, 并且确保该问题无法使用其他更好的方式来解决。
享元模式结构
1.享元 (Flyweight)
类包含原始对象中部分能在多个对象中共享的状态。 同一享元对象可在许多不同情景中使用。 享元中存储的状态被称为 “内在状态”。 传递给享元方法的状态被称为 “外在状态”
2.情景(Context)
类包含原始对象中各不相同的外在状态。情景与享元对象组成在一起就能表示原始对象的全部状态
通常情况下,原始对象的行为会保留到享元类中。因此调用享元方法需要提供外在状态作参数。但也可将行为放置在情景类中,将享元作为单纯的数据对象。
3.享元工厂(Flyweight Factory)
会对已有享元的缓存池进行管理。有了工厂后客户端就无需直接创建享元,它们只需调用工厂并向其传递目标享元的一些内在状态即可。工厂会根据参数在之前已创建的享元中进行查找,如果找到满足条件的享元就将其返回如果没有找到就根据参数新建享元。
4.客户端(Client)
必须存储和计算外在状态(情景)的数值, 因为只有这样才能调用享元对象的方法。为了使用方便,外在状态和引用享元的成员变量可以移动到单独的情景类中。
享元模式适合应用场景
仅在程序必须支持大量对象且没有足够的内存容量时使用享元模式。
实现方式
1. 将需要改写为享元的类成员变量拆分为两个部分:
内在状态: 包含不变的、 可在许多对象中重复使用的数据的成员变量。
外在状态: 包含每个对象各自不同的情景数据的成员变量
2. 保留类中表示内在状态的成员变量, 并将其属性设置为不可修改。 这些变量仅可在构造函数中获得初始数值。
3. 找到所有使用外在状态成员变量的方法, 为在方法中所用的每个成员变量新建一个参数, 并使用该参数代替成员变量。
4. 你可以有选择地创建工厂类来管理享元缓存池, 它负责在新建享元时检查已有的享元。 如果选择使用工厂, 客户端就只能通过工厂来请求享元, 它们需要将享元的内在状态作为参数传递给工厂。
5. 客户端必须存储和计算外在状态 (情景) 的数值, 因为只有这样才能调用享元对象的方法。 为了使用方便, 外在状态和引用享元的成员变量可以移动到单独的情景类中。
享元模式优缺点
优点:
✔️ 如果程序中有很多相似对象, 那么你将可以节省大量内存。
缺点:
❌ 你可能需要牺牲执行速度来换取内存, 因为他人每次调用享元方法时都需要重新计算部分情景数据。
❌代码会变得更加复杂。 团队中的新成员总是会问: “为什么要像这样拆分一个实体的状态?”。
更改后的项目
代码:
public class User {
private String name;
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public abstract class WebSite {
public abstract void use(User user);
}
public class ConcreteWebSite extends WebSite {
//共享部分(内部状态)
private String type=""; //网站发布的类型
public ConcreteWebSite(String type){
this.type = type;
}
public void use(User user){
System.out.println("使用者为"+user.getName()+" 发布的类型是:"+type);
}
}
//网站工厂类,根据需要返回一个网站
public class WebSiteFactory {
//集合,充当 池 的作用
private HashMap<String,ConcreteWebSite> pool = new HashMap<>();
//根据类型,返回一个网站。若没有,则创建并放入池中
public WebSite getWebSiteCategory(String type){
if (!pool.containsKey(type)){
pool.put(type,new ConcreteWebSite(type));
}
return pool.get(type);
}
public int getWebSiteCount(){
return pool.size();
}
}
public class Client {
public static void main(String[] args) {
WebSiteFactory factory = new WebSiteFactory();
WebSite webSite_news = factory.getWebSiteCategory("新闻");
webSite_news.use(new User("Tom"));
WebSite webSite_blogs = factory.getWebSiteCategory("博客");
webSite_blogs.use(new User("Jerry"));
WebSite webSite_blogs1 = factory.getWebSiteCategory("博客");
webSite_blogs1.use(new User("Marry"));
System.out.println(factory.getWebSiteCount());
System.out.println(webSite_blogs+" | "+webSite_blogs1); //地址一致
}
}
享元模式的注意事项和细节
1.在享元模式这样理解,“ 享”就表示共享,“元”表示对象
2.系统中有大量对象, 这些对象消耗大量内存,并且对象的状态大部分可以外部化时,我们就可以考虑选用享元模式
3.用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用HashMap/HashTable存储
4.享元模式大 大减少了对象的创建,降低了程序内存的占用,提高效率
5.享元模式提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变,这是我们 使用享元模式需要注意的地方
6.使用享元模式时,注意划分内部状态和外部状态 ,并且需要有 一个工厂类加以控制。
7.享元模式经典的应用场景是需要缓冲池的场景,比如 String常量池、数据库连接池