Jump to content

Flyweight pattern

From Wikipedia, the free encyclopedia

This is an old revision of this page, as edited by מושך בשבט (talk | contribs) at 12:49, 3 February 2021 (Undid edits by 191.187.172.154 (talk) to last revision by 152.86.252.142: nonconstructive edits). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.

In computer programming, flyweight is a software design pattern. A flyweight is an object that minimizes memory usage by sharing as much data as possible with other similar objects; it is a way to use objects in large numbers when a simple repeated representation would use an unacceptable amount of memory. Often some parts of the object state can be shared, and it is common practice to hold them in external data structures and pass them to the objects temporarily when they are used.

A classic example usage of the flyweight pattern is the data structures for graphical representation of characters in a word processor. It might be desirable to have, for each character in a document, a glyph object containing its font outline, font metrics, and other formatting data, but this would amount to hundreds or thousands of bytes for each character. Instead, for every character there might be a reference to a flyweight glyph object shared by every instance of the same character in the document; only the position of each character (in the document and/or the page) would need to be stored internally.

Another example is string interning.

In other contexts, the idea of sharing identical data structures is called hash consing.

Overview

The Flyweight [1] design pattern is one of the twenty-three well-known GoF design patterns that describe how to solve recurring design problems to design flexible and reusable object-oriented software, that is, objects that are easier to implement, change, test, and reuse.

What problems can the Flyweight design pattern solve? [2]

  • Large numbers of objects should be supported efficiently.
  • Creating large numbers of objects should be avoided.

When representing large text documents, for example, creating an object for each character in the document would result in a huge number of objects that could not be processed efficiently.

What solution does the Flyweight design pattern describe?

Define Flyweight objects that

  • store intrinsic (invariant) state that can be shared and
  • provide an interface through which extrinsic (variant) state can be passed in.

This enables clients to (1) reuse (share) Flyweight objects (instead of creating a new object each time) and (2) pass in extrinsic state when they invoke a Flyweight operation.
This greatly reduces the number of physically created objects.
Intrinsic state is invariant (context independent) and therefore can be shared (for example, the code of character 'A' in a given character set).
Extrinsic state is variant (context dependent) and therefore can not be shared and must be passed in (for example, the position of character 'A' in a text document).
See also the UML class and sequence diagram below.

History

According to the textbook Design Patterns: Elements of Reusable Object-Oriented Software,[3] the flyweight pattern was first coined and extensively explored by Paul Calder and Mark Linton in 1990 to efficiently handle glyph information in a WYSIWYG document editor,[4] although similar techniques were already used in other systems, e.g., an application framework by Weinand et al. (1988).[5] IOS's reusable cells in tableViews and collectionViews are another example of a system that uses the flywieght pattern.

Structure

UML class and sequence diagram

A sample UML class and sequence diagram for the Flyweight design pattern. [6]

In the above UML class diagram, the Client class refers (1) to the FlyweightFactory class to create/share Flyweight objects and (2) to the Flyweight interface to perform an operation by passing in extrinsic (variant) state (flyweight.operation(extrinsicState)). The Flyweight1 class implements the Flyweight interface and stores intrinsic (invariant) state that can be shared.
The sequence diagram shows the run-time interactions: The Client object calls getFlyweight(key) on the FlyweightFactory that creates and returns a Flyweight1 object. After calling operation(extrinsicState) on the returned Flyweight1 object, the Client again calls getFlyweight(key) on the FlyweightFactory, which now shares and returns the already existing Flyweight1 object.

Implementation Details & Hybrid Patterns

Mutable or immutable extrinsic state

An initial design decision in implementing a flyweight is whether to use mutable or immutable extrinsic (variant state) objects. Immutable objects can be easily shared. But this requires the creation of new extrinsic objects whenever a change in state occurs. If the objects state only occasionally changes, or changes in recurring ways, only a small number of these immutable extrinsic objects will need to be created. If the objects properties change frequently, and in unique ways, the use of mutable extrinsic objects is preferred. With a mutable implementation, objects can share state, but will not be directly modified while in use. The mutability is for better object reuse by allowing the caching & reinitialization of old, unused objects. An API must be provided in such implementations that guarantees the return of an unused, reinitialized object. When state is highly variable, such as when most objects are unique, attempts at sharing should be abandoned. In these cases, just encapsulate the highly variable state in a lightweight decorator, provide a cache for it, bulk initialize the cache, and be done with it.

Partitioning the flyweight object

One way of implementing the extrinsic (variant) component of a flyweight object is with a Decorator and using it to wrap the intrinsic object. This is preferred for state that is more variant, as decorators can be easily applied or removed from select objects, and a single object may be wrapped with multiple arbitrary decorators at runtime. Sharing of decorators is achieved by just a common reference. For the intrinsic object, a common implementation is as a class consisting of nothing but static properties. By subclassing this class and adding further static properties, you can create a variety of invariant objects. This approach can account for state that is mostly, but not entirely invariant.

Consider a text-editing application (typical example). You might highlight a paragraph of text, bolden a few words within that text... maybe italicize just one word and increase its font size. Each modification is another layer of decoration wrapping the shared, intrinsic object. Trying to implement something like this with multiple inheritance would be a complete disaster. Note that these decorators have references to one another, allowing for sharing. Messages are propagated through the decorated layers transparently.

Now consider another example, again in a word processing application. You may have 10,000 letters but only 26 unique alphabetical characters in a document. You have state that is mostly invariant - the particular letter displayed. It's not entirely invariant, but it could be easily treated as such. In such cases of mostly invariant state, subclassing the intrinsic object and adding static properties to the subclasses is the most effective optimization technique. Most compilers actually replace common objects like these - common characters, low digit numbers - using static, optimized objects. The subclassing approach has the advantage of being compiled and optimized, and the interface with the intrinsic object is kept simple via polymorphism.

These competing examples highlight the difference between hierarchical, top-down design and compositional design. In practice, objects often do not divide up neatly into variant and invariant parts, there is some grey area where you must decide how best to split up the object. As mentioned earlier, sometimes the object state is so variable that no sharing is practical, and you should just place that state in a decorator and cache it. The combined and intelligent use of the both static subclassing and decoration is what's necessary to implement a truly memory-efficient partitioning of the object.

overview of the object retrieval system

Although the Factory for creating / reusing these flyweight objects has a simple interface, this is often a Facade where the underlying system is quite complicated. Seeing as the goal of the flyweight pattern is to preserve memory, you want to try your best to cache, share, and reuse extrinsic objects wherever possible. Sharing of the extrinsic objects can be done easily by making them immutable, as mentioned earlier. Again, there are mutable implementations where sharing is possible, you simply cannot change the state of shared objects while they're being used. Instead, such a change must issue a request for either a different type of shared object, the reinitialization of an old object (the advantage of mutability), or the creation of a new object.

The specific type of cache you use generally depends on the structure of the object - whether it's mutable or immutable, and whether it can be shared or not. There are a variety of caching & retrieval algorithms to choose from. The algorithms efficiency depends on the caches data structure, number of objects, characteristics of the objects, and the memory usage patterns of the application.

To provide global access for the creation of these flyweight objects it's best to implement the factory interface as a Singleton. This should be done in both single-threaded and multi-threaded, shared memory applications. In distributed systems an alternative option is to instantiate unique instances of the entire flyweight system on each node, including the caches, though cache utilization is a potential a problem with this approach, and you will need to be very careful when bulk-initializing objects. You could come up with more complex sharing algorithms, where certain types of objects were delegated to certain nodes. You could even implement a manager that coordinates all this. That's a discussion beyond the scope of this article, though.

Generally speaking, the retrieval algorithm begins with a request for a new object via the factory interface. The request is forwarded to an appropriate cache based on what kind of object it is, how you partitioned the object, how you designed your caches & so fourth. If the request is fulfilled by an object in the cache, you may reinitialize it (if it's mutable) and return it. Otherwise a new object will need to be instantiated. If you partitioned the object into multiple extrinsic subcomponents they might need to be properly pieced together before the object is returned.

some approaches to caching flyweight objects

Caching objects with highly variable state - variable to a degree there's no reason to try sharing them - can be easily achieved using a FIFO structure. Here the FIFO structure will simply maintain unused objects in the cache, with no need to search the cache or deal with miss rates. Even so, an unmaintained cache could be preferred. There's less upfront overhead associated with an unmaintained cache: you typically initialize objects for the caches in bulk at compile time or startup. Thus the initial miss rate for an unmaintained cache should be zero for quite some time, until you run out of objects, and you won't need to perform pop operations initially. On the other hand, once you do run out the object retrieval algorithm might have more overhead associated than the push/pop operations of a maintained cache. Which type of cache to prefer here really depends on the memory usage patterns of your application, the actual data structure of the cache, the number of objects you have, & above all - the results of testing your implementation. If your application will use a very consistent number of these objects, an unmaintained cache that's bulk initialized with that exact amount of objects is preferred. If the total number of objects can vary greatly than you might want to use a maintained cache for this. Ultimately only testing will tell you which implementation to go with.

When retrieving extrinsic objects with immutable state you must simply search the cache for an object with the state you desire, if it exists. This is best done with a hash structure, hashing the state of the object. If you don't find such an object than you will have to initialize a single object with that state. This is another disadvantage to using objects with immutable state - the inability to perform bulk initializations.

When retrieving extrinsic objects with mutable state, you must search the cache for an object with the desired state and search the cache for an unused object to reinitialize if no used object is found. This approach takes more CPU cycles than other approaches, but is more memory efficient as it minimizes the total objects that must be initialized while also sharing them. It is generally preferred. A common mistake in optimizing algorithms is to optimize the CPU instructions at the expense of using more memory. Any retrieval of data from disk takes multiple orders of magnitude longer than a CPU instruction (SSD takes 8 orders of magnitude longer than CPU instruction, for example). Therefor the top priority in optimizing code is generally to minimize the memory footprint. Of course there are exceptions, but they're less common than you'd expect.

For caches like these you need a search algorithm optimized to minimize misses. There are a wide variety of cache search algorithms to choose from. It is helpful to encapsulate these selection algorithms in a Strategy pattern. This allows for easily swapping in and out different algorithms, which makes testing and optimization of the cache much more practical. In the worst case that there is no unused object available, after traversing the cache you must instantiate a new object and add it to the cache. You probably want to do a bulk allocation of objects when this occurs and add all of them to the cache, preferably with memory mapping. Note that with any cache you want enough, but not too many objects bulk-initialized, because you want to avoid excessively polluting your L1/L2 cache which can lead to thrashing

You can use separate caches for each unique subclass of extrinsic object. Multiple caches you can optimize separately, associating a unique search algorithm with each cache; or likewise a unique data structure. Do this based on testing - different caches have different memory usage patterns. Memory usage patterns also change throughout the applications execution. You can change the selection algorithm for a particular cache throughout execution. For example, when the application first runs you should have a zero miss rate, this changes as those initial bulk allocated objects get used. You want to change your selection algorithm at that point. If you encapsulated your selection algorithm in a Strategy pattern this is easy to do.

Restashing objects

The restashing algorithm is usually quite simple: override the objects deallocation function, find the appropriate cache & decrement any reference counting variables. If using a FIFO type cache you merely push the object back onto it within dealloc. If using an unshared cache other than a FIFO structure, assuming you're using a flag to track use, just update the flag in dealloc. If your cache is shared, again the objects should have a property that acts like a reference count (probably a short int), and this should be decremented in dealloc. This way you know when the object becomes unused, and is open to being mutated. If your language uses automatic reference counting you can just set the pointer to the object to nil and the reference count will automatically be decremented. Your cache search algorithm will then be able to detect the object is unused by checking its reference count.

Encapsulating the caching system

This object caching system - the factory initiating a request, the routing to caches, the caches, the retrieval algorithms, and the objects APIs - can be encapsulated in a Chain of Responsibility pattern. Doing so structures the implementation a way that promotes loose coupling between these various components. Though most request propagation logic should already encapsulated within the Strategy objects for the cache traversal algorithms, the loose coupling between components can be useful when reconfiguring your implementation (like if you need to modify it to work in a different environment, such as a multithreaded environment; if you want to test different types of caches; if you want to reuse the flyweight for a different set of objects, etc.).

Performing operations on flyweight collections

Common operations on these diverse collections include reinitializing reused extrinsic objects, iterative or single operations with conditional logic, and operations performed on certain subsets of objects. The Visitor design pattern combines very naturally with the flyweight pattern for implementing such behavior. The pattern encapsulates a behavior to be performed by a diverse collection of objects, such as the various extrinsic objects involved in the flyweight pattern. Using visitors in your code will make it better encapsulated, more readable / modifiable, and more memory-efficient than extending classes.

Immutability and equality

To enable safe sharing, between clients and threads, Flyweight objects can be made immutable. Flyweight objects are by definition value objects. The identity of the object instance is of no consequence; therefore, two Flyweight instances of the same value are considered equal.

Example in C# (note Equals and GetHashCode overrides as well as == and != operator overloads):

    public class CoffeeFlavour
    {
        public CoffeeFlavour(string flavour) => this.Flavour = flavour;
        public string Flavour { get; }
        public override boolean Equals(Object? obj) => Equals(this, obj);
        public static bool Equals(CoffeeFlavour? left, CoffeeFlavour? right) => String.Equals(left?.Flavour, right?.Flavour);
        public override int GetHashCode() => this.Flavour.GetHashCode();
        public static bool operator ==(CoffeeFlavour a, CoffeeFlavour b) => Equals(a, b);
        public static bool operator !=(CoffeeFlavour a, CoffeeFlavour b) => !Equals(a, b);
    }

Concurrency

Special consideration must be taken into account in scenarios where Flyweight objects are created on multiple threads. If the list of values is finite and known in advance, the Flyweights can be instantiated ahead of time and retrieved from a container on multiple threads with no contention. If Flyweights are instantiated on multiple threads, there are two options:

  1. Make Flyweight instantiation single threaded, thus introducing contention and ensuring one instance per value.
  2. Allow concurrent threads to create multiple Flyweight instances, thus eliminating contention and allowing multiple instances per value. This option is only viable if the equality criterion is met.

Example in C#

using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;

public interface ICoffeeFlavourFactory
{
    CoffeeFlavour GetFlavour(string flavour);
}

public class ReducedMemoryFootprint : ICoffeeFlavourFactory
{
    private readonly object _cacheLock = new object();
    private readonly IDictionary<string, CoffeeFlavour> _cache = new Dictionary<string, CoffeeFlavour>();

    public CoffeeFlavour GetFlavour(string flavour)
    {
        if (_cache.TryGetValue(flavour, out CoffeeFlavour cachedCoffeeFlavour))
            return cachedCoffeeFlavour;
        var coffeeFlavour = new CoffeeFlavour(flavour);
        ThreadPool.QueueUserWorkItem(AddFlavourToCache, coffeeFlavour);
        return coffeeFlavour;
    }

    private void AddFlavourToCache(object state)
    {
        var coffeeFlavour = (CoffeeFlavour)state;
        if (!_cache.ContainsKey(coffeeFlavour.Flavour))
        {
            lock (_cacheLock)
            {
                _cache[coffeeFlavour.Flavour] = coffeeFlavour;
            }
        }
    }
}

public class MinimumMemoryFootprint : ICoffeeFlavourFactory
{
    private readonly ConcurrentDictionary<string, CoffeeFlavour> _cache = new ConcurrentDictionary<string, CoffeeFlavour>();

    public CoffeeFlavour GetFlavour(string flavour)
    {
        return _cache.GetOrAdd(flavour, flv => new CoffeeFlavour(flv));
    }
}

Simple implementation

Flyweight allows you to share bulky data that are common to each object. In other words, if you think that same data is repeating for every object, you can use this pattern to point to the single object and hence can easily save space. Here the FlyweightPointer creates a static member Company, which is used for every object of MyObject.

// Defines Flyweight object that repeats itself.
public class Flyweight
{
    public string CompanyName { get; set; }
    public string CompanyLocation { get; set; }
    public string CompanyWebsite { get; set; }
    // Bulky data
    public byte[] CompanyLogo { get; set; }
}

public static class FlyweightPointer
{
    public static readonly Flyweight Company = new Flyweight
    {
        CompanyName = "Abc",
        CompanyLocation = "XYZ",
        CompanyWebsite = "www.example.com"
        // Load CompanyLogo here
    };
}

public class MyObject
{
    public string Name { get; set; }
    public string Company => FlyweightPointer.Company.CompanyName;
}

Example in Java

import java.util.ArrayList;
import java.util.WeakHashMap;

class CoffeeFlavour {
    private final String name;
    private static final WeakHashMap<String, CoffeeFlavour> CACHE = new WeakHashMap<>();

    // only intern() can call this constructor
    private CoffeeFlavour(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return name;
    }

    public static CoffeeFlavour intern(String name) {
        synchronized (CACHE) {
            return CACHE.computeIfAbsent(name, CoffeeFlavour::new);
        }
    }

    public static int flavoursInCache() {
        synchronized (CACHE) {
            return CACHE.size();
        }
    }
}

@FunctionalInterface
interface Order {
    void serve();

    static Order of(String flavourName, int tableNumber) {
        CoffeeFlavour flavour = CoffeeFlavour.intern(flavourName);
        return () -> System.out.println("Serving " + flavour + " to table " + tableNumber);
    }
}

class CoffeeShop {
    private final ArrayList<Order> orders = new ArrayList<>();

    public void takeOrder(String flavour, int tableNumber) {
        orders.add(Order.of(flavour, tableNumber));
    }

    public void service() {
        orders.forEach(Order::serve);
    }
}

public class FlyweightExample {
    public static void main(String[] args) {
        CoffeeShop shop = new CoffeeShop();
        shop.takeOrder("Cappuccino", 2);
        shop.takeOrder("Frappe", 1);
        shop.takeOrder("Espresso", 1);
        shop.takeOrder("Frappe", 897);
        shop.takeOrder("Cappuccino", 97);
        shop.takeOrder("Frappe", 3);
        shop.takeOrder("Espresso", 3);
        shop.takeOrder("Cappuccino", 3);
        shop.takeOrder("Espresso", 96);
        shop.takeOrder("Frappe", 552);
        shop.takeOrder("Cappuccino", 121);
        shop.takeOrder("Espresso", 121);

        shop.service();
        System.out.println("CoffeeFlavor objects in cache: " + CoffeeFlavour.flavoursInCache());
  }
}

The execution of this code will give the following :

Serving Cappuccino to table 2
Serving Frappe to table 1
Serving Espresso to table 1
Serving Frappe to table 897
Serving Cappuccino to table 97
Serving Frappe to table 3
Serving Espresso to table 3
Serving Cappuccino to table 3
Serving Espresso to table 96
Serving Frappe to table 552
Serving Cappuccino to table 121
Serving Espresso to table 121
CoffeeFlavor objects in cache: 3

Example in Python

Attributes can be defined at the class-level instead of only for instances in Python because classes are first-class objects in the language—meaning there are no restrictions on their use as they are the same as any other object. New-style class instances store instance data in a special attribute dictionary instance.__dict__. By default, accessed attributes are first looked up in this __dict__, and then fall back to the instance's class attributes next. [7] In this way, a class can effectively be a kind of Flyweight container for its instances.

Although Python classes are mutable by default, immutability can be emulated by overriding the class's __setattr__ method so that it disallows changes to any Flyweight attributes.

# Instances of CheeseBrand will be the Flyweights
class CheeseBrand:
    def __init__(self, brand: str, cost: float) -> None:
        self.brand = brand
        self.cost = cost
        self._immutable = True  # Disables future attributions

    def __setattr__(self, name, value):
        if getattr(self, "_immutable", False):  # Allow initial attribution
            raise RuntimeError("This object is immutable")
        else:
            super().__setattr__(name, value)


class CheeseShop:
    menu = {}  # Shared container to access the Flyweights

    def __init__(self) -> None:
        self.orders = {}  # per-instance container with private attributes

    def stock_cheese(self, brand: str, cost: float) -> None:
        cheese = CheeseBrand(brand, cost)
        self.menu[brand] = cheese  # Shared Flyweight

    def sell_cheese(self, brand: str, units: int) -> None:
        self.orders.setdefault(brand, 0)
        self.orders[brand] += units  # Instance attribute

    def total_units_sold(self):
        return sum(self.orders.values())

    def total_income(self):
        income = 0
        for brand, units in self.orders.items():
            income += self.menu[brand].cost * units
        return income


shop1 = CheeseShop()
shop2 = CheeseShop()

shop1.stock_cheese("white", 1.25)
shop1.stock_cheese("blue", 3.75)
# Now every CheeseShop have 'white' and 'blue' on the inventory
# The SAME 'white' and 'blue' CheeseBrand

shop1.sell_cheese("blue", 3)  # Both can sell
shop2.sell_cheese("blue", 8)  # But the units sold are stored per-instance

assert shop1.total_units_sold() == 3
assert shop1.total_income() == 3.75 * 3

assert shop2.total_units_sold() == 8
assert shop2.total_income() == 3.75 * 8

Example in Ruby

# Flyweight Object
class Lamp
  attr_reader :color
  #attr_reader makes color attribute available outside
  #of the class by calling .color on a Lamp instance

  def initialize(color)
    @color = color
  end
end

class TreeBranch
  def initialize(branch_number)
    @branch_number = branch_number
  end

  def hang(lamp)
    puts "Hang #{lamp.color} lamp on branch #{@branch_number}"
  end
end

# Flyweight Factory
class LampFactory
  def initialize
    @lamps = {}
  end

  def find_lamp(color)
    if @lamps.has_key?(color)
      # if the lamp already exists, reference it instead of creating a new one
      lamp = @lamps[color]
    else
      lamp = Lamp.new(color)
      @lamps[color] = lamp
    end
    lamp
  end

  def total_number_of_lamps_made
    @lamps.size
  end
end

class ChristmasTree
  def initialize
    @lamp_factory = LampFactory.new
    @lamps_hung = 0

    dress_up_the_tree
  end

  def hang_lamp(color, branch_number)
    TreeBranch.new(branch_number).hang(@lamp_factory.find_lamp(color))
    @lamps_hung += 1
  end

  def dress_up_the_tree
    hang_lamp('red', 1)
    hang_lamp('blue', 1)
    hang_lamp('yellow', 1)
    hang_lamp('red', 2)
    hang_lamp('blue', 2)
    hang_lamp('yellow', 2)
    hang_lamp('red', 3)
    hang_lamp('blue', 3)
    hang_lamp('yellow', 3)
    hang_lamp('red', 4)
    hang_lamp('blue', 4)
    hang_lamp('yellow', 4)
    hang_lamp('red', 5)
    hang_lamp('blue', 5)
    hang_lamp('yellow', 5)
    hang_lamp('red', 6)
    hang_lamp('blue', 6)
    hang_lamp('yellow', 6)
    hang_lamp('red', 7)
    hang_lamp('blue', 7)
    hang_lamp('yellow', 7)
    puts "Made #{@lamp_factory.total_number_of_lamps_made} total lamps"
    puts "Hung #{@lamps_hung} total lamps"
  end
end

Example in Scala

/*
run as a script using `scala flyweight.scala`
expected output:
  Serving CoffeeFlavour(Espresso) to table 121
  Serving CoffeeFlavour(Cappuccino) to table 121
  Serving CoffeeFlavour(Frappe) to table 552
  Serving CoffeeFlavour(Espresso) to table 96
  Serving CoffeeFlavour(Cappuccino) to table 3
  Serving CoffeeFlavour(Espresso) to table 3
  Serving CoffeeFlavour(Frappe) to table 3
  Serving CoffeeFlavour(Cappuccino) to table 97
  Serving CoffeeFlavour(Frappe) to table 897
  Serving CoffeeFlavour(Espresso) to table 1
  Serving CoffeeFlavour(Frappe) to table 1
  Serving CoffeeFlavour(Cappuccino) to table 2
  total CoffeeFlavour objects made: 3
*/

/* the `private` constructor ensures that only interned
 * values of type `CoffeeFlavour` can be obtained. */
class CoffeeFlavour private (val name: String){
    override def toString = s"CoffeeFlavour($name)"
}

object CoffeeFlavour {
  import scala.collection.mutable
  import scala.ref.WeakReference
  private val cache = mutable.Map.empty[String, ref.WeakReference[CoffeeFlavour]]

  def apply(name: String): CoffeeFlavour = synchronized {    
    cache.get(name) match {
      case Some(WeakReference(flavour)) => flavour
      case _ => 
        val newFlavour = new CoffeeFlavour(name)
        cache.put(name, WeakReference(newFlavour))
        newFlavour
    }
  }

  def totalCoffeeFlavoursMade = cache.size
}


case class Order(tableNumber: Int, flavour: CoffeeFlavour){

  def serve: Unit =
    println(s"Serving $flavour to table $tableNumber")
}

object CoffeeShop {
  var orders = List.empty[Order]

  def takeOrder(flavourName: String, table: Int) {
    val flavour = CoffeeFlavour(flavourName)
    val order = Order(table, flavour)
    orders = order :: orders
  }

  def service: Unit = orders.foreach(_.serve)

  def report =
    s"total CoffeeFlavour objects made: ${CoffeeFlavour.totalCoffeeFlavoursMade}"
}


CoffeeShop.takeOrder("Cappuccino", 2)
CoffeeShop.takeOrder("Frappe", 1)
CoffeeShop.takeOrder("Espresso", 1)
CoffeeShop.takeOrder("Frappe", 897)
CoffeeShop.takeOrder("Cappuccino", 97)
CoffeeShop.takeOrder("Frappe", 3)
CoffeeShop.takeOrder("Espresso", 3)
CoffeeShop.takeOrder("Cappuccino", 3)
CoffeeShop.takeOrder("Espresso", 96)
CoffeeShop.takeOrder("Frappe", 552)
CoffeeShop.takeOrder("Cappuccino", 121)
CoffeeShop.takeOrder("Espresso", 121)

CoffeeShop.service
println(CoffeeShop.report)

Example in Swift

// Instances of CoffeeFlavour will be the Flyweights
struct CoffeeFlavor : CustomStringConvertible {
    var flavor: String
    var description: String { flavor }
}

// Menu acts as a factory and cache for CoffeeFlavour flyweight objects
struct Menu {
    private var flavors: [String: CoffeeFlavor] = [:]

    mutating func lookUp(flavor: String) -> CoffeeFlavor {
        if let existing = flavors[flavor] { return existing }
        let newFlavor = CoffeeFlavor(flavor: flavor)
        flavors[flavor] = newFlavor
        return newFlavor
    }
}

struct CoffeeShop {
    private var orders: [Int: CoffeeFlavor] = [:]
    private var menu = Menu()

    mutating func takeOrder(flavor: String, table: Int) {
        orders[table] = menu.lookUp(flavor: flavor)
    }

    func serve() {
        for (table, flavor) in orders {
            print("Serving \(flavor) to table \(table)")
        }
    }
}

var coffeeShop = CoffeeShop()
coffeeShop.takeOrder(flavor: "Cappuccino", table: 1)
coffeeShop.takeOrder(flavor: "Frappe", table: 3)
coffeeShop.takeOrder(flavor: "Espresso", table: 2)
coffeeShop.takeOrder(flavor: "Frappe", table: 15)
coffeeShop.takeOrder(flavor: "Cappuccino", table: 10)
coffeeShop.takeOrder(flavor: "Frappe", table: 8)
coffeeShop.takeOrder(flavor: "Espresso", table: 7)
coffeeShop.takeOrder(flavor: "Cappuccino", table: 4)
coffeeShop.takeOrder(flavor: "Espresso", table: 9)
coffeeShop.takeOrder(flavor: "Frappe", table: 12)
coffeeShop.takeOrder(flavor: "Cappuccino", table: 13)
coffeeShop.takeOrder(flavor: "Espresso", table: 5)
coffeeShop.serve()

Example in Crystal

# Instances of CoffeeFlavor will be the Flyweights
class CoffeeFlavor
  def initialize(new_flavor : String)
    @name = new_flavor
  end

  def to_s(io)
    io << @name
  end
end

# Menu acts as a factory and cache for CoffeeFlavor flyweight objects
class Menu
  def initialize
    @flavors = {} of String => CoffeeFlavor
  end

  def lookup(flavor_name : String)
    @flavors[flavor_name] ||= CoffeeFlavor.new(flavor_name)
  end

  def total_flavors_made
    @flavors.size
  end
end

# Order is the context of the CoffeeFlavor flyweight.
class Order
  private getter table_number : Int32, flavor : CoffeeFlavor

  def initialize(@table_number, @flavor)
  end

  def serve
    puts "Serving #{flavor} to table #{table_number}"
  end
end

class CoffeeShop
  private getter orders
  private getter menu

  def initialize
    @orders = [] of Order
    @menu = Menu.new
  end

  def take_order(flavor_name : String, table : Int32)
    flavor = menu.lookup(flavor_name)
    order = Order.new(table, flavor)
    @orders << order
  end

  def service
    orders.each do |order|
      order.serve
    end
  end

  def report
    "Total CoffeeFlavor made: #{menu.total_flavors_made}"
  end
end

# Program
shop = CoffeeShop.new
shop.take_order("Cappuchino", 2)
shop.take_order("Frappe", 1)
shop.take_order("Espresso", 1)
shop.take_order("Frappe", 897)
shop.take_order("Cappuccino", 97)
shop.take_order("Frappe", 3)
shop.take_order("Espresso", 3)
shop.take_order("Cappuccino", 3)
shop.take_order("Espresso", 96)
shop.take_order("Frappe", 552)
shop.take_order("Cappuccino", 121)
shop.take_order("Espresso", 121)

shop.service
puts shop.report

Output

Serving Cappuchino to table 2
Serving Frappe to table 1
Serving Espresso to table 1
Serving Frappe to table 897
Serving Cappuccino to table 97
Serving Frappe to table 3
Serving Espresso to table 3
Serving Cappuccino to table 3
Serving Espresso to table 96
Serving Frappe to table 552
Serving Cappuccino to table 121
Serving Espresso to table 121
Total CoffeeFlavor made: 4

Example in C++

The C++ Standard Template Library provides several containers that allow unique objects to be mapped to a key. The use of containers helps further reduce memory usage by removing the need for temporary objects to be created.

#include <iostream>
#include <map>
#include <string>

// Instances of Tenant will be the Flyweights
class Tenant {
public:
    Tenant(const std::string& name = "") : m_name(name) {}

    std::string name() const {
        return m_name;
    }
private:
    std::string m_name;
};

// Registry acts as a factory and cache for Tenant flyweight objects
class Registry {
public:
    Registry() : tenants() {}

    Tenant findByName(const std::string& name) {
        if (tenants.count(name) != 0) return tenants[name];
        auto newTenant = Tenant(name);
        tenants[name] = newTenant;
        return newTenant;
    }
private:
    std::map<std::string,Tenant> tenants;
};

// Apartment maps a unique tenant to their room number.
class Apartment {
public:
    Apartment() : m_occupants(), m_registry() {}

    void addOccupant(const std::string& name, int room) {
        m_occupants[room] = m_registry.findByName(name);
    }

    void tenants() {
        for (auto i : m_occupants) {
            const int room = i.first;
            const Tenant tenant = i.second;
            std::cout << tenant.name() << " occupies room " << room << std::endl;
        }
    }
private:
    std::map<int,Tenant> m_occupants;
    Registry m_registry;
};

int main() {
    Apartment apartment;
    apartment.addOccupant("David", 1);
    apartment.addOccupant("Sarah", 3);
    apartment.addOccupant("George", 2);
    apartment.addOccupant("Lisa", 12);
    apartment.addOccupant("Michael", 10);
    apartment.tenants();

    return 0;
}

See also

References

  1. ^ Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison Wesley. pp. 195ff. ISBN 978-0-201-63361-0.{{cite book}}: CS1 maint: multiple names: authors list (link)
  2. ^ "The Flyweight design pattern - Problem, Solution, and Applicability". w3sDesign.com. Retrieved 2017-08-12.
  3. ^ Gamma, Erich; Richard Helm; Ralph Johnson; John Vlissides (1995). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley. pp. 205–206. ISBN 978-0-201-63361-0.
  4. ^ Calder, Paul R.; Linton, Mark A. (October 1990). Glyphs: Flyweight Objects for User Interfaces. The 3rd Annual ACM SIGGRAPH Symposium on User Interface Software and Technology. Snowbird, Utah, United States. pp. 92–101. doi:10.1145/97924.97935. ISBN 0-89791-410-4.
  5. ^ Weinand, Andre; Gamma, Erich; Marty, Rudolf (1988). ET++—an object oriented application framework in C++. OOPSLA (Object-Oriented Programming Systems, Languages and Applications). San Diego, California, United States. pp. 46–57. CiteSeerX 10.1.1.471.8796. doi:10.1145/62083.62089. ISBN 0-89791-284-5.
  6. ^ "The Flyweight design pattern - Structure and Collaboration". w3sDesign.com. Retrieved 2017-08-12.
  7. ^ "Data Model §". The (online) Python Language Reference. Python Software Foundation. Retrieved 7 March 2017.