Singletonizer

From APIDesign

(Difference between revisions)
Jump to: navigation, search
Current revision (05:41, 5 November 2024) (edit) (undo)
 
(6 intermediate revisions not shown.)
Line 1: Line 1:
-
[[Singletonizer]] is a simplification of [[Factory]] by use of [[Generics]]. It is very useful pattern for creating [[ProviderAPI]]. On the other hand, it is not that useful for [[ClientAPI]], as its use requires an [[OpenClass]] concept.
+
[[Singletonizer]] (called [[ObjectAlgebras]] in newer literature) is a simplification of [[Factory]] by use of [[Generics]]. It is very useful pattern for creating [[ProviderAPI]]. On the other hand, it is not that useful for [[ClientAPI]], as its use requires an [[OpenClass]] concept. [[Singletonizer]] is the simplest way to emulate [[Abstract Data Type]]s in [[OOP]] languages.
 +
 
 +
 
 +
== Real Example ==
 +
 
 +
[[Graal]] '''Graph I/O''' [[API]] is using the [[singletonizer]] pattern in its [http://www.graalvm.org/graphio/javadoc/org/graalvm/graphio/package-summary.html javadoc].
 +
 
 +
== Artificial Sample ==
Instead of having one class representing a [[Factory]] which creates another class representing the ''data'', one can move all the methods into one class:
Instead of having one class representing a [[Factory]] which creates another class representing the ''data'', one can move all the methods into one class:
Line 9: Line 16:
<source lang="java" snippet="day.end.bridges.CountingDigestor"/>
<source lang="java" snippet="day.end.bridges.CountingDigestor"/>
-
[[Singletonizer]] is useful when representing complex graphs or tree hierarchies. One can have a class in a [[ClientAPI]] that represents each graph's [[wikipedia::Graph_Vertex|vertex]]. There can be tons of its instances. Yet plugging in behaviour for each instance can be quite simple. They can all delegate to single [[Singletonizer]] implementation which handles all their operation. Thus they are basically ''singletonizing'' behaviour of many instances to one - hence the name [[Singletonizer]].
+
[[Singletonizer]] is useful when representing complex graphs or tree hierarchies. One can have a class in a [[ClientAPI]] that represents each graph's [[wikipedia::Graph_Vertex|vertex]]. There can be tons of its instances. Yet plugging in behaviour for each instance can be quite simple. They can all delegate to single [[Singletonizer]] implementation which handles all their operation. Thus they are basically ''singletonizing'' behaviour of many instances to one - hence the name [[Singletonizer]]:
 +
 
 +
<source lang="java" snippet="day.end.bridges.DigestImpl"/>
 +
 
 +
Btw. the ''DigestImpl'''s ''ACCESSOR'' field, which slightly complicates the code, is an example of [[FriendPackages|friend accessor pattern]] allowing us to access '''protected''' methods of ''Digestor'' class located in different package.
<comments/>
<comments/>
Line 15: Line 26:
[[Category:APIDesignPatterns]]
[[Category:APIDesignPatterns]]
[[Category:APIDesignPatterns:Creational]]
[[Category:APIDesignPatterns:Creational]]
-
[[Category:APIDesignPatterns:Accuracy]]
+
[[Category:APIDesignPatterns:Clarity]]

Current revision

Singletonizer (called ObjectAlgebras in newer literature) is a simplification of Factory by use of Generics. It is very useful pattern for creating ProviderAPI. On the other hand, it is not that useful for ClientAPI, as its use requires an OpenClass concept. Singletonizer is the simplest way to emulate Abstract Data Types in OOP languages.


Real Example

Graal Graph I/O API is using the singletonizer pattern in its javadoc.

Artificial Sample

Instead of having one class representing a Factory which creates another class representing the data, one can move all the methods into one class:

Code from Digestor.java:
See the whole file.

public abstract class Digestor<Data> {
   protected abstract byte[] digest(Data data);
   protected abstract Data create(String algorithm); 
   protected abstract void update(Data data, ByteBuffer input);
 
}
 

This simplifies the life for the implementors of the Digestor class while retaining the same power as they had before. They have to subclass and implement just one type. Yet they can also have own data type. Moreover, due to use of Generics they do not need to deal with any casts or unsafe type checking. Or, just like in following example, one can reuse suitable existing data type:

Code from CountingDigestor.java:
See the whole file.

public final class CountingDigestor extends Digestor<int[]> {
    @Override
    protected byte[] digest(int[] data) {
        int i = data[0];
        byte[] arr = { 
            (byte) (i & 255), 
            (byte) ((i >> 8) & 255), 
            (byte) ((i >> 16) & 255), 
            (byte) ((i >> 24) & 255) 
        };
        return arr;
    }
 
    @Override
    protected int[] create(String algorithm) {
        return "cnt".equals(algorithm) ? new int[1] : null;
    }
 
    @Override
    protected void update(int[] data, ByteBuffer input) {
        data[0] += input.remaining();
        input.position(input.position() + input.remaining());
    }
}
 

Singletonizer is useful when representing complex graphs or tree hierarchies. One can have a class in a ClientAPI that represents each graph's vertex. There can be tons of its instances. Yet plugging in behaviour for each instance can be quite simple. They can all delegate to single Singletonizer implementation which handles all their operation. Thus they are basically singletonizing behaviour of many instances to one - hence the name Singletonizer:

Code from DigestImpl.java:
See the whole file.

final class DigestImpl<Data> {
    private static final DigestorAccessorImpl ACCESSOR;
    static {
        ACCESSOR = new DigestorAccessorImpl();
    }
 
    private final Digestor<Data> digestor;
    private final String algorithm;
    private Data data;
 
    private DigestImpl(Digestor<Data> digestor, String algorithm, Data d) {
        this.digestor = digestor;
        this.algorithm = algorithm;
        this.data = d;
    }
 
    static <Data> DigestImpl create(
        Digestor<Data> digestor, String algorithm
    ) {
        // indirectly calls digestor.create(algorithm)
        Data d = ACCESSOR.create(digestor, algorithm);
        if (d == null) {
            return null;
        } else {
            return new DigestImpl(digestor, algorithm, d);
        }
    }
 
    byte[] digest(ByteBuffer bb) {
        // indirectly calls digestor.update(data, bb)
        ACCESSOR.update(digestor, data, bb);
        // indirectly calls digestor.digest(data)
        return ACCESSOR.digest(digestor, data);
    }
}
 

Btw. the DigestImpl's ACCESSOR field, which slightly complicates the code, is an example of friend accessor pattern allowing us to access protected methods of Digestor class located in different package.

<comments/>

Personal tools
buy