From: Tom McGlynn on 29 Jul 2010 12:34 On Jul 29, 10:39 am, Alan Gutierrez <a...(a)blogometer.com> wrote: > Tom McGlynn wrote: > > On Jul 29, 7:20 am, Tom Anderson <t...(a)urchin.earth.li> wrote: > >> On Tue, 27 Jul 2010, Tom McGlynn wrote: > >>> On Jul 26, 1:33 pm, Tom Anderson <t...(a)urchin.earth.li> wrote: > >>>> On Mon, 26 Jul 2010, Tom McGlynn wrote: > >>>>> On Jul 26, 8:01 am, Tom Anderson <t...(a)urchin.earth.li> wrote: > >>>>>> But Joshua was talking about using instances of Color, where those > >>>>>> instances are singletons (well, flyweights is probably the right term > >>>>>> when there are several of them) > >>>>> I don't think flyweights is the right word. For me flyweights are > >>>>> classes where part of the state is externalized for some purpose. This > >>>>> is orthogonal to the concept of singletons. E.g., suppose I were > >>>>> running a simulation of galaxy mergers of two 100-million-star > >>>>> galaxies. Stars differ only in position, velocity and mass. Rather > >>>>> than creating 200 million Star objects I might create a combination > >>>>> flyweight/singleton Star where each method call includes an index that > >>>>> is used to find the mutable state in a few external arrays. > >>>> I am 90% sure that is absolutely not how 'flyweight' is defined in the > >>>> Gang of Four book > >>> Here's a bit of what the GOF has to say about flyweights. (Page 196 > >>> in my version).... > >>> "A flyweight is a shared object that can be used in multiple contexts > >>> simultaneously. The flyweight acts as an independent object in each > >>> context--it's indistinguishable from an instance of the object that's > >>> not shared.... The key concept here is the distinction between intrinsic > >>> and extrinsic state. Intrinsic state is stored in the flyweight. It > >>> consists of information that's independent of the flyweight's context, > >>> thereby making it shareable. Extrinsic state depends on and varies with > >>> the flyweights context and therefore can't be shared. Client objects > >>> are responsible for passing extrinsic state to the flyweight when it > >>> needs it." > >>> That's reasonably close to what I had in mind. > >> Yes, point taken. I'm still not happy with your usage, though. > > >> IIRC, the example in GoF is of a Character class in a word processor. So, > >> a block of text is a sequence of Character objects. Each has properties > >> like width, height, vowelness, etc and methods like paintOnScreen. But > >> because every lowercase q behaves much the same as every other lowercase > >> q, rather than having a separate instance for every letter in the text, we > >> have one for every distinct letter. > > >> The extrinsic state in this example is the position in the text, the > >> typeface, the style applied to the paragraph, etc. Certainly, things that > >> are not stored in the Character. But also not things that intrinsically > >> belong in the Character anyway; rather, things inherited from enclosing > >> objects. > > >> Whereas in your case, the array offset *is* something intrinsic to the > >> Star. If it had been something else, say the coordinates of the centre of > >> mass of the local cluster, then i'd agree that that was Flyweightish. But > >> i'm not so sure about the array index. > > > Hmmm.... The GOF has the location of the letter as extrinsic state, > > while I'm suggesting the location of the star. Seems pretty > > comparable. My use of the > > array index is simply the way I supply the extrinsic information, it's > > not intrinsic to a given Star [and in fact the index of the same > > 'Star' might change during the simulation, since the array is likely > > to be resorted continually in the process]. I wonder if the sticking > > point is the lack of any internal state. But the GOF notes that the > > FlyWeight is particularly applicable when "Most object state can be > > made extrinsic". This doesn't mean that Star isn't a real class. We > > can have lots of methods in the Star class, e.g., > > distanceFromNeighbor(i), move(), accelerate(), force(), ... > > I don't think the state of the `Star` can be extract from the `Star`. > The idea behind the Word Processor example is that you can cache the > font size in an object along with the character code itself, and have an > object that can be reused and reset into the document at any location, > and then participate in a `Composite` pattern, where the characters > participate as tiny graphical objects. > > But this implies that 11pt Helvetica 'C' is one object that is reused. > I'm assuming that each of these `Star` objects will have different vales > entirely, therefore `Flyweight` does not apply much at all. > > I've called this a tiny `Adaptor` because you're going to take a > `MappedByteBuffer` or parallel arrays of primitives, or something > structure that stores the state of the object, and when you need an > object, create a temporary wrapper around the state of a `Star` stored > at a particular index. > By the GOF's definition an Adapter is used to convert one interface to another. So given what I would call a FlyWeight style interface interface IndexedStar { double[] getPosition(i) } then if you want to have a interface NonIndexedStar { double[] getPosition() } you could create an adapter class class StarAdapter { int index; IndexedStar base; StarAdapter(int i, IndexedStar star} { index = i; this.base = star; } double[] getPosition() { return base.getPosition(i); } } [I'm not suggesting this is a good way to go, just trying to clarify what the terms mean to me.] Why is the position of a star any more 'intrinsic' to the star, than the position of a character is to the character? I think the difference in perception comes from the fact that as this toy problem has been set up, there is no internal state for the star. Suppose we make the problem a little more complex. We have 25 classes of star: 5 ages x 5 spectral types with 4,000,000 of each. Now there are 25 flyweight instances which have different masses, temperatures, brightness, color, metallicity, magnetic fields, whatever... If I want to generate an image of the simulation at some time, I need to use the internal state of the flyweights to generate the image just as we need the actual patterns of each glyph to generate a page of text. If this isn't a flyweight what's missing? If it is, note that I could be using an identical mechanisms to store the position/ velocity... as before. So I think the issue is that people are unfamiliar with FlyWeights with little internal state but perhaps I'm missing some more essential difference. Maybe this goes back to your thoughts on this being an adapter, which I'll recast in a positive way: Use of a no-internal state flyweight can be used to as an adapter to give an object oriented interface for elements of arrays or other structures. Regards, Tom McGlynn
From: Alan Gutierrez on 31 Jul 2010 21:06 Tom McGlynn wrote: > On Jul 29, 10:39 am, Alan Gutierrez <a...(a)blogometer.com> wrote: >> Tom McGlynn wrote: >>> On Jul 29, 7:20 am, Tom Anderson <t...(a)urchin.earth.li> wrote: > >> I don't think the state of the `Star` can be extract from the `Star`. >> The idea behind the Word Processor example is that you can cache the >> font size in an object along with the character code itself, and have an >> object that can be reused and reset into the document at any location, >> and then participate in a `Composite` pattern, where the characters >> participate as tiny graphical objects. >> >> But this implies that 11pt Helvetica 'C' is one object that is reused. >> I'm assuming that each of these `Star` objects will have different vales >> entirely, therefore `Flyweight` does not apply much at all. >> >> I've called this a tiny `Adaptor` because you're going to take a >> `MappedByteBuffer` or parallel arrays of primitives, or something >> structure that stores the state of the object, and when you need an >> object, create a temporary wrapper around the state of a `Star` stored >> at a particular index. >> > > By the GOF's definition an Adapter is used to convert one interface to > another. So given what I would call a FlyWeight style interface > > interface IndexedStar { > double[] getPosition(i) > } > > then if you want to have a > interface NonIndexedStar { > double[] getPosition() > } > > you could create an adapter class > > class StarAdapter { > int index; > IndexedStar base; > StarAdapter(int i, IndexedStar star} { > index = i; > this.base = star; > } > double[] getPosition() { > return base.getPosition(i); > } > } > > [I'm not suggesting this is a good way to go, just trying > to clarify what the terms mean to me.] > > Why is the position of a star any more 'intrinsic' to the star, than > the position of a character is to the character? I think the > difference in perception comes from the fact that as this toy problem > has been set up, there is no internal state for the star. Suppose we > make the problem a little more complex. We have 25 classes of star: > 5 ages x 5 spectral types with 4,000,000 of each. Now there are 25 > flyweight instances which have different masses, temperatures, > brightness, color, metallicity, magnetic fields, whatever... If I > want to generate an image of the simulation at some time, I need to > use the internal state of the flyweights to generate the image just as > we need the actual patterns of each glyph to generate a page of text. > If this isn't a flyweight what's missing? If it is, note that I > could be using an identical mechanisms to store the position/ > velocity... as before. > > So I think the issue is that people are unfamiliar with FlyWeights > with little internal state but perhaps I'm missing some more essential > difference. > > Maybe this goes back to your thoughts on this being an adapter, which > I'll recast in a positive way: Use of a no-internal state flyweight > can be used to as an adapter to give an object oriented interface for > elements of arrays or other structures. On another branch of this thread, I wrote some code that actually compiles, that described a `BigList` that had an `ElementIO`. I'm going to put it here again for your reference. Hope no one minds. package comp.lang.java.programmer; import java.nio.ByteBuffer; public interface ElementIO<T> { public void write(ByteBuffer bytes, int index, T item); public T read(ByteBuffer bytes, int index); public int getRecordLength(); } package comp.lang.java.programmer; import java.nio.MappedByteBuffer; import java.util.AbstractList; public class BigList<T> extends AbstractList<T> { private final ElementIO<T> io; private final MappedByteBuffer bytes; private int size; public BigList(ElementIO<T> io, MappedByteBuffer bytes, int size) { this.io = io; this.bytes = bytes; this.size = size; } // result is not `==` to value `set` so only use element type that // defines `equals` (and `hashCode`). @Override public T get(int index) { return io.read(bytes, index * io.getRecordLength()); } @Override public T set(int index, T item) { if (index < 0 || index >= size) { throw new IndexOutOfBoundsException(); } T result = get(index); io.write(bytes, index * io.getRecordLength(), item); return result; } @Override public void add(int index, T element) { size++; // probably off by one, but you get the idea... for (int i = size - 2; i >= index; i--) { set(index + 1, get(index)); } set(index, element); } // and `remove` and the like, but of course only `get`, `set` // and `add` to the very end can be counted on to be performant. @Override public int size() { return size; } } With these generics we could build an implementation of `StarIO`. public class StarIO implements ElementIO<Star> { public void write(ByteBuffer bytes, int index, Star item) { bytes.putDouble(index, item.getPosition[0]); bytes.putDouble(index + (Double.SIZE / Byte.SIZE), item.getPosition[0]); } public Star read(ByteBuffer bytes, int index) { double[] position = new double[] { bytes.getLong(index), bytes.getLong(index + (Double.SIZE / Byte.SIZE)) }; return new Star(position); } public int getRecordLength() { return (Double.SIZE / Byte.SIZE) * 2; } } Usage is then like one big list. When you write, you don't actually put the `Star` in an array, you write out the values. When you read you create a new `Star` read from the underlying `MappedByteBuffer`. Therefore, to my mind, the `Star` is an Adaptor for the `MappedByteBuffer` where the strategy for an Adaptor is to: Convert the interface of a class into another interface clients expect. Adaptor lets classes work together that couldn't otherwise because of incompatible interfaces. Whereas the Flyweight strategy is to: Use sharing to support large numbers of fine grained objects efficiently. The solution we are discussing address the problem of supporting large numbers of fine grained efficiently, but not through sharing. Thus, there's an opportunity to name a new pattern, if you'd like. I am not arguing semantics, I don't think, but really trying to understand what patterns are in play. The interesting concept that makes one think of flyweight is that the Adaptor is short-lived, which is why, when you say Flyweight, the name seems apropos, but I believe there's a pattern here that needs its own name. This is the pattern that is used by any of the ORM tools. Create a short lived typed object around a string or binary data for the sake of the client. -- Alan Gutierrez - alan(a)blogometer.com - http://twitter.com/bigeasy
From: Tom McGlynn on 3 Aug 2010 00:02
On Jul 31, 9:06 pm, Alan Gutierrez <a...(a)blogometer.com> wrote: > public class StarIO implements ElementIO<Star> { > public void write(ByteBuffer bytes, int index, Star item) { > bytes.putDouble(index, item.getPosition[0]); > bytes.putDouble(index + (Double.SIZE / Byte.SIZE), > item.getPosition[0]); > } > > public Star read(ByteBuffer bytes, int index) { > double[] position = new double[] { > bytes.getLong(index), > bytes.getLong(index + (Double.SIZE / Byte.SIZE)) > }; > return new Star(position); > } > > public int getRecordLength() { While this might be a fine way to implement things, I don't think Star's created this way are FlyWeights. It looks like all of a Star's state is internal so they are just standard objects. By definition (at least the GOF's) FlyWeights have external state. Not that you have to use FlyWeight's but it was in trying to illustrate them that I brought up the example. > Usage is then like one big list. When you write, you don't actually put > the `Star` in an array, you write out the values. When you read you > create a new `Star` read from the underlying `MappedByteBuffer`. > > Therefore, to my mind, the `Star` is an Adaptor for the > `MappedByteBuffer` where the strategy for an Adaptor is to: > > Convert the interface of a class into another interface clients expect. > Adaptor lets classes work together that couldn't otherwise because of > incompatible interfaces. For me an Adaptor class doesn't change the semantics of the interface but just the details of the implementation. E.g., one interface has double getX(); double getY(); double getZ(); and the other has double[] getPosition(); They both have the idea of "get the position", but differ in the implementation details. An Adapter bridges the difference and allows a Class expecting objects of the first type to use the second. n your example I see our Star as an implementation of the MappedByteBuffer but I'm not sure which -- if any -- of the GOF patterns the relationship of Star and MappedByteBuffer represents for me. > > Whereas the Flyweight strategy is to: > > Use sharing to support large numbers of fine grained objects efficiently. > > The solution we are discussing address the problem of supporting large > numbers of fine grained efficiently, but not through sharing. Not sure what you are saying here. My original approach had sharing. There was a single actual star instance that's shared by each logical star. How much more sharing can you get! My guess is that it's the fact that sharing is taken to the limit that causes some of the discomfort people evinced here. They don't like a FlyWeight that uses only a single actual instance. But you're right that you don't need FlyWeight's to address the issue. Your approach using an external cache might well work fine though I'd be concerned if I had to create an object every access. Even the relatively efficient object creation that Java now has needs to be amortized over a fair bit of computation. Perhaps your approach could be called something like 'lazy transient instantiation with an external cache'. But it wouldn't have made my original point of demonstrating the orthogonality of FlyWeights and Singletons. > > Thus, there's an opportunity to name a new pattern, if you'd like. > > I am not arguing semantics, I don't think, but really trying to > understand what patterns are in play. The interesting concept that makes > one think of flyweight is that the Adaptor is short-lived, which is why, > when you say Flyweight, the name seems apropos, but I believe there's a > pattern here that needs its own name. For me FlyWeight's need to share external state. They need to be 'lighter' than an equivalent 'normal' object would be. My guess is that a lot of this has to do with the relationship you want to emphasize. Given implementations will weave multiple patterns together. By picking out one aspect we may emphasize one pattern. > > This is the pattern that is used by any of the ORM tools. Create a short > lived typed object around a string or binary data for the sake of the > client. > It's always more interesting when other people's views differ from one's own (though perhaps less gratifying), and I've found the the discussion very intriguing and it's alway useful to reread the source text. Regards, Tom |