Jump to content
Mike Torrettinni

Should I keep class alive (global) or not?

Recommended Posts

Not sure how to ask this, but I'm not sure how to 'properly' use class information: do I keep class alive in memory to access data at any time (global), or create and free class on demand and save data to variables (global)?

 

Here is example of Check for translation file update example - I have 3 units:

- Main Form with Check button

- Check Form

- CheckForTranslation class

 

image.thumb.png.c42519f95499f29f66a6a24e8bbd5be0.png

 

So, from Main Form I call Check form which creates local CheckForTranslation class variable in FormShow, uses class methods to check and display information if any new translation files are available.

Check Form gives me option to download file, if I want to.

 

Now I would to keep info if new translation file is available, so I don't need to keep checking (until next project restart). So, the options I see:

1) Create CheckForTranslation in Main Form global variable and use it when needed (in Check form). This allows me to always access results from checking, and just keep all data available at any time, any where. But, is global class variable, which I try to avoid, if possible.

2) Only create CheckForTranslation  in methods as needed ( and free class at the end of method) and save status in global variables so that status is always available - this option only saves a status variables (maybe 2 - gNewTranlsationFileAvailabe: boolean and gNewTranlsationFileDownloadLink: string).

 

So, some information I will definitely need to keep around, a class or simple status variables.

I'm not sure what is good approach to create such class, when to free it and how to use the information from it.

 

Any advice would be helpful!

 

 

 

 

Share this post


Link to post

While it is generally a good idea to avoid global variables there are cases where they can be useful, and your case is one of them. Do you know what a singleton object is?

 

A singleton is a good solution for a scenario where you need to store some data internally in your application that you have to access from several different places in your code (different units, for instance). You place the class into a unit of its own. The unit is then used by all other units that need access to the singleton.

 

There are different ways to implement a singleton object. The simplest one is to actually never create an instance of the class at all. Instead you use class variables and class properties to hold the data. That works well if all data you need available are simple or compiler-managed types (ordinal types, numeric types, strings). It works less well if you need more complex types, e.g. other objects. This can be handled now that Delphi has class constructors and class destructors, however.

 

My preferred way to implement singletons is to only expose an interface type that has the necessary properties and methods to store and retrieve the data, and a factory method that returns an instance of the interface. The class implementing the interface is private to the unit (declared and implemented in its implementation section). The factory method creates an instance of the class on the first call to it and stores the interface obtained from it in a variable only visible in the unit implementation section. The unit gets a finallization section that sets the interface to nil to finally destroy the singleton.

 

  • Like 1

Share this post


Link to post

Aha, I thought singleton is just 'then way you use class' thing. I use records for what you described above - a data container (global variable) without needed for creating an instance, while classes are used with .Create, so I guess this means I always uses classes as created instances.

So, are you describing a record (with properties, methods...)? I need to access it from Main Form and Check form, and perhaps other units, too.

Share this post


Link to post
19 minutes ago, Mike Torrettinni said:

Aha, I thought singleton is just 'then way you use class' thing. I use records for what you described above - a data container (global variable) without needed for creating an instance, while classes are used with .Create, so I guess this means I always uses classes as created instances.

So, are you describing a record (with properties, methods...)? I need to access it from Main Form and Check form, and perhaps other units, too.

A record is about equivalent to using class variables. The main difference is the syntax you use to access the data.

// a record as global storage
var
  gDataStore: record
    Data1: string;
    ....
    end;
    
// accessed as
//  someLocalVar :=  dDataStore.Data1;

// class as global storage
type
  TDataStore = class
  private
  class var
    FData1: string;
    ...
  public
    class property Data1: string read FData1 write FData1;
    ...
  end;
  
// uses as
// someLocalVar := TDataStore.Data1;

    
    

As you can see there is no global variable, and using the Delphi syntax rules it is clear that you accessing a class here. You can also better control access to the data and use setter and getter methods if necessary.

Share this post


Link to post

Ok, lets assume we call method/property SetData1to set Data1 value... then how do I access same Data1 value from Main Form and Check form? Do I need to use someLocalVar := TDataStore.Data1everywhere or is someLocalVar actually global variable, gGlobalDataStore?

Share this post


Link to post
2 hours ago, Mike Torrettinni said:

Ok, lets assume we call method/property SetData1to set Data1 value... then how do I access same Data1 value from Main Form and Check form? Do I need to use someLocalVar := TDataStore.Data1everywhere or is someLocalVar actually global variable, gGlobalDataStore?

No, you just use TDatastore.Data1 whereever you need the value stored in that field. Basically the class vars of the TDatastore class are your global variables, they are just neatly packaged and hidden in the class.

  • Like 1

Share this post


Link to post
59 minutes ago, PeterBelow said:

No, you just use TDatastore.Data1 whereever you need the value stored in that field. Basically the class vars of the TDatastore class are your global variables, they are just neatly packaged and hidden in the class.

Thank you, interesting. And this is singleton, right?

Share this post


Link to post
5 hours ago, PeterBelow said:

.My preferred way to implement singletons is to only expose an interface type that has the necessary properties and methods to store and retrieve the data, and a factory method that returns an instance of the interface.

@PeterBelow I prefer the same, and use it in many places like that.

But not all the time (usually these classes are called seldomly), but there is always a tiny chance that these singletons might crash because they are not threadsafe.

I never had such case, but its always on my mind that it could in some cases, where those failures would be hard to find.

Would you recommend or not recommend to make such simple data storage classes generally threadsafe ?

 

Share this post


Link to post

Well, once you start writing unit tests you will know why global vars/singletons are a bad choice.

Share this post


Link to post
Posted (edited)

@Schokohase

I do write untit tests, and I know about the problems that globals and threads can create.

Still I think in many cases, like above data storage which are seldomly accessed, the risk may be acceptable.

Also not many Singleton implementations out there seems to care about thread safety.

So does your post mean that you would generally recommend to make the global singletons threadsafe ?

Edited by Rollo62

Share this post


Link to post

I do not use Singleton implementations.

 

If the application needs one, I cover that with a DI container so there will be a single instance within the whole application scope.

 

Thread safety is a total different story and I cover that when needed.

  • Like 1

Share this post


Link to post
6 hours ago, PeterBelow said:

A singleton is a good solution for

A singleton is never a good solution - it might be the easy one in the middle of a DI unfriendly architecture but that's it.

 

Singletons do not only couple pieces together that don't have to but they also allow all kinds of crazy and hard to find errors - as every global state does.

Share this post


Link to post

Stefan et al,

 

  I am an old fogey who generally keeps programming in a style you'd recognize as Mostly 80's Enlightened. Problem is, it's thirty/fourty years later and Delphi has moved on. I've spent some effort this spring trying to get into Interfaces. Read Nick Hodges' first book, browsed through the second and stare blank-faced at the third on my reading list. I'm getting maybe half and that's actually a tribute to Nick. I'm the old dog trying to learn new tricks.

 

  This thread has hammered away at global vars and singletons. And I can't see my way around them. At startup, my apps normally load a record called Globals. In there, the user info gets stored, as it's referred by a lot of code. Plus program settings and other miscellany. I DID have them as UserName, UserShortCode, UserSecurityLevel, but graduated (apparently, barely) to Globals.UserName, Globals.UserShortCode, etc.

 

  Now, I am looking at an interface approach. I have a date pair that comes from ranging requests in many reports and queries. The interface I'm fooling around with, in a completely separate unit, would lead to DateRange.Date1, DateRange.Date2, DateRange.DateSpan, DateRange.Date1Str, DateRange.Date1EasyRead, etc. There's more. A bunch of properties that I have logged using a Date in any fashion. My idea is to do all these transformations and have them done and ready when I need to create an interface, populate it with either two copies of the same date or the two actual dates, and then use a property immediately. I plan to dispose of the interface if no longer needed, but occasionally to keep it around while the form (say, the Report Manager) is active.

 

  This plan looks dodgy from what I've read here. I'm TRYING to look at this from other angles, but I still come back to global vars to hold items of interest, whether straight old-fashioned variables, record contents or interfaces. Can you point me in the right direction?

 

  Thank you for your patience. GM

 

And I apologize for semi-hijacking this thread

Share this post


Link to post
2 hours ago, Stefan Glienke said:

A singleton is never a good solution - it might be the easy one in the middle of a DI unfriendly architecture but that's it.

 

Singletons do not only couple pieces together that don't have to but they also allow all kinds of crazy and hard to find errors - as every global state does.

@Stefan Glienke What would be your suggestion on my original question, how to keep data alive - to keep the class alive or store important data from class and free the class?

Share this post


Link to post
21 hours ago, Mike Torrettinni said:

Thank you, interesting. And this is singleton, right?

In a way, yes. The class itself exists only once, so all its class vars also exist only once in the application memory. The beauty is that it is impossible to create a second "instance" of the data, even if you were to create an object of that class it would still only access the class variables.

 

The singleton pattern is classically implemented using a class of which only one instance can be created, which will typically live as long as the application is running. Using a "normal" class for that has its problems, since such classses are designed to allow the creation of any number of instances (objects) of that class. It is also hard to prevent code using the singleton object from destroying it. Using class variables eliminates all these problems, and using the interface to a hidden object method does this as well, although malicious code can in fact prematurely destroy the singleton object by calling _Release on the interface, at least if lifetime management by reference counting is used (hidden class derived from TInterfacedObject). But that is easy enough to prevent as well, just by using a class with a implementation of the IInterface methods that does not use reference counting for lifetime control.

Share this post


Link to post
21 hours ago, Rollo62 said:

@PeterBelow I prefer the same, and use it in many places like that.

But not all the time (usually these classes are called seldomly), but there is always a tiny chance that these singletons might crash because they are not threadsafe.

I never had such case, but its always on my mind that it could in some cases, where those failures would be hard to find.

Would you recommend or not recommend to make such simple data storage classes generally threadsafe ?

 

I have a code template for creating a singleton that uses a thread-safe factory method, and making the interface methods thread-safe is also no big deal, since all the code is hidden in the implementation section of the unit. I don't do that by default, though, only when the singleton is actually used by several threads.

Share this post


Link to post
21 hours ago, Schokohase said:

Well, once you start writing unit tests you will know why global vars/singletons are a bad choice.

Singletons are no problem for unit tests, you just need to make them implement an additional interface that resets all the singleton fields to the startup state for the test build. That is the called from the Setup method of the test case.

Share this post


Link to post
19 hours ago, Stefan Glienke said:

A singleton is never a good solution - it might be the easy one in the middle of a DI unfriendly architecture but that's it.

 

Singletons do not only couple pieces together that don't have to but they also allow all kinds of crazy and hard to find errors - as every global state does.

It all depends on what type of application you write. Singletons are, in my opinion, a good and easy to implement solution for apps that require some global state and are fairly small and focused on specific tasks, i. e. a typical one-user desktop application. They would certainly not be a good solution for a large distributed application with many modules and build by a whole team of developers. There are no panaceas in software development, be pragmatic, not dogmatic 😉.

Share this post


Link to post
26 minutes ago, PeterBelow said:

Singletons are no problem for unit tests, you just need to make them implement an additional interface that resets all the singleton fields to the startup state for the test build. That is the called from the Setup method of the test case.

My tests are running sometimes in parallel - and it can be done without any problems and without any special code only for testing 

  • Like 1

Share this post


Link to post

I am not going to argue with you why mutable(!) global state is bad because a ton of respected people in the software development community have already proven that.

As I said a singleton can be the easy solution - that does not make it a good one.

 

Putting any code into an otherwise no or hard testable software component is only a crutch - there are ways to design software to be testable by default which does not only make it easily testable but also more robust and maintainable.

Just read "Clean Code" and/or watch "Clean code talks" on Youtube.

 

Also fwiw there is a difference between Singleton as in the GoF Singleton (ensuring that there is only one instance and preventing everyone from ever instantiating a second instance of that) and "singletoness" (establishing the contract of only having one instance but not preventing anyone from instantiating one on its own and inject it somewhere). Even in DI driven architectures there are singletons, but it is controlled via the DI system that there is only one instance being created and passed around. That decouples any consumer from the actual implementation of that singleton and makes it easily testable/mockable (see "seam")

  • Like 1

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×