# 享元模式

## 模式说明

某些对象相似度很高，只有少量字段或方法不同，当需要大量这类对象时，若为每一次需求都创建实例，内存开销会很大。此时可以提炼出这些对象的相同部分，作为所谓的享元，创建一个享元工厂类，持有享元实例。需要使用上述对象时，从享元工厂中获取该实例。若这些对象有不同部分，提取这些不同部分单独成类，使用时作为享元中的方法参数传入享元(使相同和不同部分结合)。这样无论使用多少次，对于享元而言，只有一个实例的开销。

本示例以网站为抽象享元，博客网站和电子商务网站为具体享元，用户为外部非享元。展示如何通过享元工厂类添加和获取享元，如何将非享元作为享元方法的参数传入到享元内。

\\

## 结构

**抽象享元类**: 被使用对象中提炼出的相同内容的集合，定义享元的抽象方法。 **具体享元类**: 继承抽象享元类，实现抽象方法。 **非享元类**: 需要使用但不能被提炼为享元内容的部分，单独成类，需要时传入享元。 **享元工厂类**: 持有具体享元实例，有添加享元和获取享元的方法。

\\

## 代码演示

```java
package com.yukiyama.pattern.structure;

import java.util.HashMap;
import java.util.Map;

/**
 * 享元模式
 */
public class FlyweightDemo {

    public static void main(String[] args) {
        // 声明享元工厂
        WebsiteFactory fa = new WebsiteFactory();
        // 声明具体享元博客网站和电子商务网站
        Website blog = new BlogWebsite("Blog");
        Website ec = new BlogWebsite("EC");
        // 向享元工厂中添加如上两种享元
        fa.addFlyweight(blog);
        fa.addFlyweight(ec);
        // 通过享元工厂，创建两个博客网站和两个电子商务网站
        Website blog1 = fa.getFlyweight("Blog");
        Website blog2 = fa.getFlyweight("Blog");
        Website ec1 = fa.getFlyweight("EC");
        Website ec2 = fa.getFlyweight("EC");
        // 将两个博客网站和两个电子商务网站分给不同的使用者
        blog1.use(new User("莫小言"));
        blog2.use(new User("金大庸"));
        ec1.use(new User("马风"));
        ec2.use(new User("刘强西"));
        // 如下均输出“true”，即通过享元工厂获取的多个享元以及
        // 工厂内持有的享元均为同一个。
        System.out.println(blog == blog1 && blog1 == blog2);
        System.out.println(ec == ec1 && ec1 == ec2);
    }

}

/**
 * 享元工厂类
 * 以Map数据结构持有享元，key为网站类型，value为Website实例。
 * 实现添加享元，获取享元的方法。
 * 客户端声明享元工厂后，需要继续创建享元并将其添加进享元工厂中。
 */
class WebsiteFactory{
    private Map<String, Website> flyweights = new HashMap<>();
    
    public void addFlyweight(Website web) {
        if(!flyweights.containsKey(web.getCatagory())) {
            flyweights.put(web.getCatagory(), web);
        } else {
            System.out.println("已存在该享元。");
        }
    }
    
    public Website getFlyweight(String key) {
        if(!flyweights.containsKey(key)) {
            System.out.println("无此享元。");
            return null;
        } else {
            return flyweights.get(key);
        }
    }
    
}

/**
 * 享元抽象类
 * 声明享元的字段和相关方法。
 */
abstract class Website{
    private String catagory;
    
    public Website(String catagory) {
        this.catagory = catagory;
    }
    public String getCatagory() {
        return catagory;
    }
    public abstract void use(User user);
}

/**
 * 具体享元类
 * 继承享元抽象类，实现抽象方法。
 * 如下是博客网站类。
 */
class BlogWebsite extends Website{
    public BlogWebsite(String catagory) {
        super(catagory);
    }
    @Override
    public void use(User user) {
        System.out.printf("这是一个%s网站，提供文章发布服务。\n", getCatagory());
        System.out.printf("网站用户为%s。\n", user.getUser());
    }
}

/**
 * 具体享元类
 * 如下是电子商务网站类。
 */
class ECWebsite extends Website{
    public ECWebsite(String catagory) {
        super(catagory);
    }
    @Override
    public void use(User user) {
        System.out.printf("这是一个%s网站，提供商品发布服务。\n", getCatagory());
        System.out.printf("网站用户为%s。\n", user.getUser());
    }
}

/**
 * 非享元类
 * 享元Website需要结合非享元User使用，例如对博客网站来说，他们可能共用
 * 相同的文章编辑器控件(作为享元的一部分)，但各自使用的用户不同(非享元)。
 */
class User{
    private String user;
    
    public User(String user) {
        this.user = user;
    }
    public String getUser() {
        return user;
    }
}
```
