Sharing a Singleton for read-only access to data

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

Sharing a Singleton for read-only access to data

Tim Johnson-2
Hello Squeakers,

It is cool that we have this forum for asking these kinds of questions.

-- TL;DR -- I learned I probably shouldn't subclass a singleton even though it seemed so easy;  I wonder instead how to design my controller properly in an MVC paradigm;  should I learn about Factory methods and add them to my singleton?;  should I keep query methods out of my data-holding Singletons and move them to new Presenter objects as intermediaries between data-holding Singletons and views?; should I read a book?; should I give up and go home?.

-- THE TASK -- I am working on a little Seaside app for my job, with a maximum of ~4 simultaneous users.  It started as skunkworks but has proved kind of useful.  My model has a couple of singletons for a couple of collections of data represented as (say) MyRecords or XYZRecords.  Singletons seemed good because the source materials being parsed into these records are ~4.0 MB apiece, so I don't want multiple copies of the data hanging around, and I only need read-only access.

Maybe the singletons could be described like this:

#MyReader class
   "Singleton;  Reads some CSV or other formats;  parses into MyRecords; makes collections of MyRecords available using different query methods"



For a quick-and-dirty solution to get a demo up-and-running to show my boss, I subclassed one of my Singletons (oh no!) as FilteredMyReader and added #filterByYear:, #filterBlock:, and #resetFilter methods. Inside this subclass, I just overrode the accessor for the XYZRecords to something like this:

#records
^ records select: (self filterBlock)

with some helper methods:

#filterByYear: aYear
self filterBlock: [:ea | ea year = aYear]


... and #resetFilter ...

self filterBlock: [:ea | true ]

With these new methods in my subclass, I had a drop-in replacement for my MyReader.  It worked great!  All of my query-style and-reporting-style methods were still available (because it is a subclass), but because the accessor method now was filtered, they showed only the data I wanted.  Woo-hoo!

-- BUT THERE CAME TO BE A PROBLEM -- the big problem arose when multiple sessions (multiple users) were accessing the app at the same time.  Seasoned Smalltalk vets are sure to chuckle to themselves now, but I am not exactly sure why this happened (subclassing singletons is not threadsafe?).  I think when one session would change the filterBlock, it would affect another session's view into the data.  So one user may be intending to view 2016 but it may actually show 2012.  (When designing this quick-and-dirty solution, I may have mistakenly thought that each Session had a new instance of my new FilteredMyReader subclass, but probably not [!], because it was a subclass of a singleton [!]).

So, I have reached the philosophical point where I get to ask "is this is a bug in my implementation, or a flaw in my design?"  A quick Google of "subclass a singleton" brings up a number of results akin to "don't do this, but if you have to, here's how: " so I think my quick-and-dirty solution might be at fault.

-- SO -- as I ponder whether the flaw is in my implementation or my design, I question whether the proper approach   and a working solution would be to stop subclassing the Singleton and instead do one of the following:

1) In the Singleton, keep my query/reporting methods, but instead of returning Bags or OrderedCollections or what-have-you, have them manufacture (like a Factory?) instances of a to-be-created class of object which would be like a 'looking glass' into the data, like an intermediary between the Seaside component and the model.  Not sure what the terminology would be -- Presenter?  Controller?  I think "Art and Science of Smalltalk" (Simon Lewis) might have called it a Presenter or a PluggableSomething.

2) Similar to (1) but skip any interaction between the Singleton and the View/Session altogether.   Create a new XYZPresenter or Controller or PluggableSomething class which can have many instances around, without holding copies of the Singleton's data.  Create these on-demand from my WASession subclass (controller) or in my components (view).  These new objects will interact with the Singleton themselves in a read-only fashion.  

3) Give up on Singletons for holding data and keep this stuff in a database outside of the image.  My coworkers want a traditional relational database anyway.

After writing all of this up, I am still a bit confused how to proceed, because the HPI Seaside tutorial I read many years ago says a WASession subclass is supposed to be my "Controller."  So maybe I should leverage /it/ more to access the singletons / model (without storing copies of the data!).  But maybe I still would be best served by creating an additional intermediary between my WASession subclass (as controller) and the Singletons (as /part/ of the model).

I hope this wasn't too much to follow, and might prove fun and instructive for someone. (Please note that while I may make references to what may be some Design Patterns, I haven't fully read the books or the Smalltalk companion, so I am speaking only with some infantile understanding of what I am talking about.)

Thanks,
Tim


_______________________________________________
Beginners mailing list
[hidden email]
http://lists.squeakfoundation.org/mailman/listinfo/beginners
Reply | Threaded
Open this post in threaded view
|

Re: Sharing a Singleton for read-only access to data

Ben Coman
Hi Tim,

I'm not clear on which of your methods are instance-side or
class-side. Can you clarify this using the form..
XYZRecords >> filterByYear  -- for instance side
XYZRecords class >> filterByYear  -- for class side

I'm not overly familiar with Seaside or Design Pattern architectures,
but it seems you want multiple filter instances accessing one copy of
the data.  So maybe try putting your data in a class variable.
Something like this...

Object subclass: #XYZRecords
    instanceVariableNames: 'filterBlock'
    classVariableNames: 'Data'
    ...

XYZRecords class >> readFromCSVFile: aFile
    Data := OrderedCollection new.
    "for all lines from file..."
         Data add: ......

XYZRecords >> filterByYear: aYear
    filterBlock := [ :ea | ea year = aYear]

XYZRecords>>records
     ^ Data select: filterBlock

XYZRecords class >> filterByYear:  aYear
    ^self new filterByYear:  aYear


_Example usage_

XYZRecords readFromCSVFile: '~user/data.csv'.

recordset1 := XYZRecords filterByYear: 1971
recordset2 := XYZRecords filterByYear: 2000

recordset1 records inspect.
recordset2 records inspect.

No need to necessarily reset the filter.  You might just throw the
recordset instance away.

cheers -ben

On Fri, May 13, 2016 at 1:01 AM, Tim Johnson <[hidden email]> wrote:

> Hello Squeakers,
>
> It is cool that we have this forum for asking these kinds of questions.
>
> -- TL;DR -- I learned I probably shouldn't subclass a singleton even though it seemed so easy;  I wonder instead how to design my controller properly in an MVC paradigm;  should I learn about Factory methods and add them to my singleton?;  should I keep query methods out of my data-holding Singletons and move them to new Presenter objects as intermediaries between data-holding Singletons and views?; should I read a book?; should I give up and go home?.
>
> -- THE TASK -- I am working on a little Seaside app for my job, with a maximum of ~4 simultaneous users.  It started as skunkworks but has proved kind of useful.  My model has a couple of singletons for a couple of collections of data represented as (say) MyRecords or XYZRecords.  Singletons seemed good because the source materials being parsed into these records are ~4.0 MB apiece, so I don't want multiple copies of the data hanging around, and I only need read-only access.
>
> Maybe the singletons could be described like this:
>
> #MyReader class
>    "Singleton;  Reads some CSV or other formats;  parses into MyRecords; makes collections of MyRecords available using different query methods"
>
>
>
> For a quick-and-dirty solution to get a demo up-and-running to show my boss, I subclassed one of my Singletons (oh no!) as FilteredMyReader and added #filterByYear:, #filterBlock:, and #resetFilter methods. Inside this subclass, I just overrode the accessor for the XYZRecords to something like this:
>
> #records
> ^ records select: (self filterBlock)
>
> with some helper methods:
>
> #filterByYear: aYear
> self filterBlock: [:ea | ea year = aYear]
>
>
> ... and #resetFilter ...
>
> self filterBlock: [:ea | true ]
>
> With these new methods in my subclass, I had a drop-in replacement for my MyReader.  It worked great!  All of my query-style and-reporting-style methods were still available (because it is a subclass), but because the accessor method now was filtered, they showed only the data I wanted.  Woo-hoo!
>
> -- BUT THERE CAME TO BE A PROBLEM -- the big problem arose when multiple sessions (multiple users) were accessing the app at the same time.  Seasoned Smalltalk vets are sure to chuckle to themselves now, but I am not exactly sure why this happened (subclassing singletons is not threadsafe?).  I think when one session would change the filterBlock, it would affect another session's view into the data.  So one user may be intending to view 2016 but it may actually show 2012.  (When designing this quick-and-dirty solution, I may have mistakenly thought that each Session had a new instance of my new FilteredMyReader subclass, but probably not [!], because it was a subclass of a singleton [!]).
>
> So, I have reached the philosophical point where I get to ask "is this is a bug in my implementation, or a flaw in my design?"  A quick Google of "subclass a singleton" brings up a number of results akin to "don't do this, but if you have to, here's how: " so I think my quick-and-dirty solution might be at fault.
>
> -- SO -- as I ponder whether the flaw is in my implementation or my design, I question whether the proper approach   and a working solution would be to stop subclassing the Singleton and instead do one of the following:
>
> 1) In the Singleton, keep my query/reporting methods, but instead of returning Bags or OrderedCollections or what-have-you, have them manufacture (like a Factory?) instances of a to-be-created class of object which would be like a 'looking glass' into the data, like an intermediary between the Seaside component and the model.  Not sure what the terminology would be -- Presenter?  Controller?  I think "Art and Science of Smalltalk" (Simon Lewis) might have called it a Presenter or a PluggableSomething.
>
> 2) Similar to (1) but skip any interaction between the Singleton and the View/Session altogether.   Create a new XYZPresenter or Controller or PluggableSomething class which can have many instances around, without holding copies of the Singleton's data.  Create these on-demand from my WASession subclass (controller) or in my components (view).  These new objects will interact with the Singleton themselves in a read-only fashion.
>
> 3) Give up on Singletons for holding data and keep this stuff in a database outside of the image.  My coworkers want a traditional relational database anyway.
>
> After writing all of this up, I am still a bit confused how to proceed, because the HPI Seaside tutorial I read many years ago says a WASession subclass is supposed to be my "Controller."  So maybe I should leverage /it/ more to access the singletons / model (without storing copies of the data!).  But maybe I still would be best served by creating an additional intermediary between my WASession subclass (as controller) and the Singletons (as /part/ of the model).
>
> I hope this wasn't too much to follow, and might prove fun and instructive for someone. (Please note that while I may make references to what may be some Design Patterns, I haven't fully read the books or the Smalltalk companion, so I am speaking only with some infantile understanding of what I am talking about.)
>
> Thanks,
> Tim
>
>
> _______________________________________________
> Beginners mailing list
> [hidden email]
> http://lists.squeakfoundation.org/mailman/listinfo/beginners
_______________________________________________
Beginners mailing list
[hidden email]
http://lists.squeakfoundation.org/mailman/listinfo/beginners
Reply | Threaded
Open this post in threaded view
|

Re: Sharing a Singleton for read-only access to data

Tim Johnson-2
Hi Ben,

Good ideas.  I think your solution is the way to go.  Throwing out the XYZRecords instances when I am done, rather than resetting filters, is a good idea.  Keeping the data as a class variable is also a great tip.  I was keeping the "Singleton" instance as the class variable instead of just keeping the data like that.  Silly.

Thanks for the help and for the tip on notation.  My current implementation was similar to what you wrote -- with the filtering / querying methods instance-side, and CSV parsing stuff class-side.

Best,
Tim



On May 12, 2016, at 6:43 PM, Ben Coman wrote:

> Hi Tim,
>
> I'm not clear on which of your methods are instance-side or
> class-side. Can you clarify this using the form..
> XYZRecords >> filterByYear  -- for instance side
> XYZRecords class >> filterByYear  -- for class side
>
> I'm not overly familiar with Seaside or Design Pattern architectures,
> but it seems you want multiple filter instances accessing one copy of
> the data.  So maybe try putting your data in a class variable.
> Something like this...
>
> Object subclass: #XYZRecords
>    instanceVariableNames: 'filterBlock'
>    classVariableNames: 'Data'
>    ...
>
> XYZRecords class >> readFromCSVFile: aFile
>    Data := OrderedCollection new.
>    "for all lines from file..."
>         Data add: ......
>
> XYZRecords >> filterByYear: aYear
>    filterBlock := [ :ea | ea year = aYear]
>
> XYZRecords>>records
>     ^ Data select: filterBlock
>
> XYZRecords class >> filterByYear:  aYear
>    ^self new filterByYear:  aYear
>
>
> _Example usage_
>
> XYZRecords readFromCSVFile: '~user/data.csv'.
>
> recordset1 := XYZRecords filterByYear: 1971
> recordset2 := XYZRecords filterByYear: 2000
>
> recordset1 records inspect.
> recordset2 records inspect.
>
> No need to necessarily reset the filter.  You might just throw the
> recordset instance away.
>
> cheers -ben
>
> On Fri, May 13, 2016 at 1:01 AM, Tim Johnson <[hidden email]> wrote:
>> Hello Squeakers,
>>
>> It is cool that we have this forum for asking these kinds of questions.
>>
>> -- TL;DR -- I learned I probably shouldn't subclass a singleton even though it seemed so easy;  I wonder instead how to design my controller properly in an MVC paradigm;  should I learn about Factory methods and add them to my singleton?;  should I keep query methods out of my data-holding Singletons and move them to new Presenter objects as intermediaries between data-holding Singletons and views?; should I read a book?; should I give up and go home?.
>>
>> -- THE TASK -- I am working on a little Seaside app for my job, with a maximum of ~4 simultaneous users.  It started as skunkworks but has proved kind of useful.  My model has a couple of singletons for a couple of collections of data represented as (say) MyRecords or XYZRecords.  Singletons seemed good because the source materials being parsed into these records are ~4.0 MB apiece, so I don't want multiple copies of the data hanging around, and I only need read-only access.
>>
>> Maybe the singletons could be described like this:
>>
>> #MyReader class
>>   "Singleton;  Reads some CSV or other formats;  parses into MyRecords; makes collections of MyRecords available using different query methods"
>>
>>
>>
>> For a quick-and-dirty solution to get a demo up-and-running to show my boss, I subclassed one of my Singletons (oh no!) as FilteredMyReader and added #filterByYear:, #filterBlock:, and #resetFilter methods. Inside this subclass, I just overrode the accessor for the XYZRecords to something like this:
>>
>> #records
>> ^ records select: (self filterBlock)
>>
>> with some helper methods:
>>
>> #filterByYear: aYear
>> self filterBlock: [:ea | ea year = aYear]
>>
>>
>> ... and #resetFilter ...
>>
>> self filterBlock: [:ea | true ]
>>
>> With these new methods in my subclass, I had a drop-in replacement for my MyReader.  It worked great!  All of my query-style and-reporting-style methods were still available (because it is a subclass), but because the accessor method now was filtered, they showed only the data I wanted.  Woo-hoo!
>>
>> -- BUT THERE CAME TO BE A PROBLEM -- the big problem arose when multiple sessions (multiple users) were accessing the app at the same time.  Seasoned Smalltalk vets are sure to chuckle to themselves now, but I am not exactly sure why this happened (subclassing singletons is not threadsafe?).  I think when one session would change the filterBlock, it would affect another session's view into the data.  So one user may be intending to view 2016 but it may actually show 2012.  (When designing this quick-and-dirty solution, I may have mistakenly thought that each Session had a new instance of my new FilteredMyReader subclass, but probably not [!], because it was a subclass of a singleton [!]).
>>
>> So, I have reached the philosophical point where I get to ask "is this is a bug in my implementation, or a flaw in my design?"  A quick Google of "subclass a singleton" brings up a number of results akin to "don't do this, but if you have to, here's how: " so I think my quick-and-dirty solution might be at fault.
>>
>> -- SO -- as I ponder whether the flaw is in my implementation or my design, I question whether the proper approach   and a working solution would be to stop subclassing the Singleton and instead do one of the following:
>>
>> 1) In the Singleton, keep my query/reporting methods, but instead of returning Bags or OrderedCollections or what-have-you, have them manufacture (like a Factory?) instances of a to-be-created class of object which would be like a 'looking glass' into the data, like an intermediary between the Seaside component and the model.  Not sure what the terminology would be -- Presenter?  Controller?  I think "Art and Science of Smalltalk" (Simon Lewis) might have called it a Presenter or a PluggableSomething.
>>
>> 2) Similar to (1) but skip any interaction between the Singleton and the View/Session altogether.   Create a new XYZPresenter or Controller or PluggableSomething class which can have many instances around, without holding copies of the Singleton's data.  Create these on-demand from my WASession subclass (controller) or in my components (view).  These new objects will interact with the Singleton themselves in a read-only fashion.
>>
>> 3) Give up on Singletons for holding data and keep this stuff in a database outside of the image.  My coworkers want a traditional relational database anyway.
>>
>> After writing all of this up, I am still a bit confused how to proceed, because the HPI Seaside tutorial I read many years ago says a WASession subclass is supposed to be my "Controller."  So maybe I should leverage /it/ more to access the singletons / model (without storing copies of the data!).  But maybe I still would be best served by creating an additional intermediary between my WASession subclass (as controller) and the Singletons (as /part/ of the model).
>>
>> I hope this wasn't too much to follow, and might prove fun and instructive for someone. (Please note that while I may make references to what may be some Design Patterns, I haven't fully read the books or the Smalltalk companion, so I am speaking only with some infantile understanding of what I am talking about.)
>>
>> Thanks,
>> Tim
>>
>>
>> _______________________________________________
>> Beginners mailing list
>> [hidden email]
>> http://lists.squeakfoundation.org/mailman/listinfo/beginners
> _______________________________________________
> Beginners mailing list
> [hidden email]
> http://lists.squeakfoundation.org/mailman/listinfo/beginners
>

_______________________________________________
Beginners mailing list
[hidden email]
http://lists.squeakfoundation.org/mailman/listinfo/beginners
Reply | Threaded
Open this post in threaded view
|

Re: Sharing a Singleton for read-only access to data

Stephan Eggermont-3
In reply to this post by Tim Johnson-2
Just a small flaw, easy to fix.

You can just subclass the session class and add the filter data to that,
or you can add user management.

In QcMagritte we use announcements and user management, so there
we have

WASession subclass: #QCSession
     instanceVariableNames: 'announcer user'
     classVariableNames: ''
     category: 'QC-Magritte-Session'

with methods like

#isLoggedIn
     ^self user notNil

where the seaside application class (class side) does

#registerAt: anApplicationName
     ^(WAAdmin register: self asApplicationAt: anApplicationName)
         preferenceAt: #sessionClass put: self sessionClass;
         addLibrary: JQDeploymentLibrary;
         addLibrary: JQUiDeploymentLibrary;
         yourself

and the seaside component does

#isLoggedIn
     ^self session isLoggedIn

If you want the filters to be coupled to the user instead of the
session, your users can also be such a singleton collection.
Otherwise, just use a session class that holds onto your filter data.

Seaside itself (on Squeak and Pharo) is threadsafe

Stephan

_______________________________________________
Beginners mailing list
[hidden email]
http://lists.squeakfoundation.org/mailman/listinfo/beginners