Nick Hodges

Delphi Development Pretty Good Practices #4 – Do Work in Classes

05 May

The next principle for the “Pretty Good Practices” we’ll discuss is this notion:  Whenever possible and as much as possible, put functionality in a class –  preferably a class that can be easily unit tested, reused, and separated from any user interface.

TextScrubber demonstrates this via the use of the TTextScrubber class in the uTextScrubber.pas unit.  TTextScrubber  is a simple TObject descendant that does all the work for the whole application, really.  It is a standalone class – you could take the uTextScrubber.pas unit and use it in most any project you cared to.  Because of this, it is also very easy to write unit tests for this class.  (We covered unit testing in my previous series “Fun with Testing DateUtils.pas”, but I’ll discuss Unit Testing in a later post in this series as well.)  The class attempts to follow the “Law of Demeter”, which says that classes should know as little as possible about outside entities.  The three principles of the Law of Demeter are as follows:

  • Each class should have only limited or hopefully no knowledge of other classes.
  • If a class must have knowledge of other classes, it should only have connections to classes that know about it as well.
  • Classes should never “reach through” one class to talk to a third class

In the case of TTextScrubber, it only knows about and utilizes the TClipboard class and nothing else.  It doesn’t try to grab things out of TClipboard or attach to or require any other class.  It pretty much minds its own business, utilizes the services of the clipboard, and provide an easy way to get at its functionality.  It endeavors to do one thing:  scrub text, by both straightening and “un-formatting” it.  It has short, sweet method bodies, and ensures that it doesn’t try to do too much beyond exactly what it is supposed to do.  Following the Law of Demeter tends to make your code more maintainable and reusable. By reducing dependencies, you ensure that a class is as flexible as possible and that changes to it don’t tend to have far reaching consequences. 

So, to as large a degree as possible, you should endeavor to put the functionality of your program into classes.  One way to tell you are not doing this is if you tend to do “OnClick” programming, or relying on event handlers to do the work of your application.  The Pretty Good Practices way of programming would dictate that your event handlers would contain code that merely instantiated and used other classes instead of having the actual code in them to do the work of your application. 

So for instance, most of the work in TextScrubber gets done in an OnClick event of the TTrayIcon component.  That code looks like this:


procedure TStraightTextMainForm.MainTrayIconClick(Sender: TObject);
begin
  MainTrayIcon.Animate := True;
  case TextScrubberOptions.ClickChoice of
    ccStraightenText:
      begin
        DoStraightenText;
      end;
    ccScrubClipboard:
      begin
        DoPurifyText;
      end;
  end;
end;

It merely calls one of two functions, DoStraigthenText or DoPurifyText, that scrub the text on the clipboard.  Those two methods look pretty much the same – they merely create a TTextScrubber, use it, and then free it.  DoStraightenText looks like this:


procedure TStraightTextMainForm.DoStraightenText;
var
  TS: TTextScrubber;
begin
  TS := TTextScrubber.Create(TextScrubberOptions.ShouldTrim);
  try
    TS.StraightenTextOnClipboard;
  finally
    TS.Free;
  end;
end;

This method is very simple and to the point — it utilizes the TTextScrubber class to do the work.  It’s not always entirely possible, but I try to make as many of  my event handlers and methods follow this pattern of merely utilizing the functionality of external classes.  Doing so enables a few things:

  • It means that functionality is much easier to unit test.  Isolated classes with specific functionality make unit testing really easy. 
  • Functionality is easier to share and reuse.  An isolated, decoupled class can easily be moved to new applications as it has few or no dependencies.
  • Lean event handlers mean that your user interface isn’t tightly coupled to the work code.  This means that adjusting or altering your UI is easier to do, and adjusting and altering the work code doesn’t mean a change in the way the UI works.

So, to sum up – always try to build standalone classes to do the work or your application. 

17 Responses to “Delphi Development Pretty Good Practices #4 – Do Work in Classes”

  1. 1
    Magno Machado Paulo Says:

    Nick, what kind of functionality do you recommend to put in classes? Or do you try to put everything?
    For instance, whould you put things like IntToStr() into classes?
    There are people which put literaly everything into classes, so they end up with lots of classes that only have lots untility functions wrapped into class methods.

  2. 2
    Rick Wheeler Says:

    I agree with Magno, could you please explain the best way for "utility" functions. Every programmer on the planet as a toolkit developed over many years which are shared between projects. Is it best to wrap these in classes or leave as global functions in generic units? To me, some things do not "belong" to a class and it makes no sense to construct/destruct an object just to encapsulate the functionality.

  3. 3
    Mason Wheeler Says:

    Gotta agree with Rick. I’ve seen a lot of code, and even some in the Delphi RTL, that tries to put utility functions in classes simply for the sake of having it in a class. Delphi’s Generics implementation even encourages this, since you can’t have a standalone generic function.

    But this is really ugly, and anyone who writes "classes" like that ought to be ashamed of themselves.. An object is a collection of variables bound to a set of methods. A set of functions with no shared state is not an object, it’s a collection of functions.

    Just because Java and .NET got it wrong and created an ugly object model full of abstraction inversions doesn’t mean Delphi (or Delphi developers) need to follow their bad example.

  4. 4
    Jolyon Smith Says:

    Java and .NET are/were simply too idealistic. They, as with a lot of "new/modern language features" are designed in a rarified intellectual atmosphere, largely devoid of practical considerations. People sit around and talk in highly theoretical and idealistic terms about how code should be written in the "purest" form.

    Many modern additions to those language have been artifical attempts to address pragmatic concerns without compromising the "OO purity" of the languages… anonymous methods are very useful when your language doesn’t support first class functions, for example.

    Pascal always struck the right balance between pragmatism and helpfulness and it’s depressing to see Embarcadero directing their efforts into polluting the language with concepts from other languages simply to "keep up" whilst true and more pressing needs are neglected.

  5. 5
    Mason Wheeler Says:

    @Joylon: Exactly. OOP is good because it helps you write code that’s easier to understand, and because it provides inheritance and polymorphism. If what you’re doing doesn’t require inheritance and polymorphism, and wouldn’t gain any advantages to code readability from putting it in a class, then OOP is not good for it.

    It’s a tool, not a Gospel principle.

  6. 6
    Delphi Energy Reports Strong Financial and Operational Results for First Quarter 2010 Says:

    [...] Nick Hodges » Blog Archive » Delphi Development Pretty G&#959&#959&#1281 Practices #4 &n… [...]

  7. 7
    Ken Knopfli Says:

    I Agree With Nick… in principle. (wink to anyone following the UK elections)…but also Joylon/Mason.

    One of the unique strengths of Delphi is that you can still have units with simple routines, and have them globally accessible. Languages that enforce the object model often lead to juggling static/creation order problems and granularity and You-Aint-Gonna-Need-It issues. Until you see a class suggesting itself in your routines, there is no need to overdo it. Classes can become Tamagotchis.

  8. 8
    Wodzu Says:

    I couldn’t agree more with you guys! Not everything must be wrapped by a class. This tendency to imitate "mainstream" languages is really worry me. (However, I do understand that mainstream = $ ). Delphi is in the process of becoming less Delphi and more something like D# this days…

  9. 9
    Anthony Frazier Says:

    I agree with the others that "not everything must be in a class."

    I’m curious what many of you think about the records in IOUtils. They’re basically collections of static functions/procedures put into a record basically to act as a namespace. Worthwhile, or should that unit have been split into IOUtils.Directory, IOUtils.Path, and IOUtils.File?

  10. 10
    Bruce McGee Says:

    I agree with a lot in this post except the use of the word "always".

    I think the biggest take away should be the comment about OnClick programming. It doesn’t matter so much if you put non-UI code in classes or methods as long as it’s not in your form events.

  11. 11
    Dean Millam Says:

    I agree with all of you (Nick included). In order to understand what Nick really thinks about utility methods you only have to look as far as his NixUtils.pas included in his project. Certainly utility methods are fine outside of classes but when you have a class that is supposed to exhibit some behavior it is good form to encapsulate that behavior within methods of the class. Some of the utility functions the class needs can be located outside the class however it is not a good idea to make the class to heavily dependent on other classes in order to complete its intended behavior.

    Thanks for the articles Nick I am reading them all intently.

  12. 12
    Jan Derk Says:

    If you want to always use classes, you are much better of dropping Delphi and switching to Java or C#.

    One of the great strengths of Delphi is that the programmer has a choice to use classes where they help and skip them where they don’t.

  13. 13
    mansi Says:

    Hi, you have provided a detailed and nice information on how to proceed with delphi development

  14. 14
    Programmer Guy Says:

    Slightly OT, but Nick, can you reconsider the template of your site. Light-ish text on a dark basckground is hard to read.

  15. 15
    sx2008 Says:

    Delphi allows nested functions/procedures. A nice and helpfull feature.
    I think everybody has seen functions with more than 3 nested functions/procedures like this:

    function CalculateSomething(arg1:integer;arg2:string):string;
    function AddSomething;
    begin …. end;
    // more nested functions/procedures here

    var

    begin

    end;

    In my opinion this is a **very strong** indicator that you should refactor this code to a class.
    So look out for nested functions and change the litte monsters to a class.

  16. 16
    Nick Hodges Says:

    sx2008 –

    I agree. That is good advice.

    Nick

  17. 17
    Alan Clark Says:

    I personally prefer putting utility methods into a static class, for Nicks’s reasons and also -

    1) Decreases namespace pullution - Code insight list is shorter when you have less global methods
    2) Compatability with Delphi Prism, where everything is a class (excluding Prism’s global namespace hack)
    3) Elegance - I find TFile.Exists(FileName) nicer than FileExists(FileName).

    But as others have said the ability to have global procedures and static classes is a strength of Delphi.

© 2014 Nick Hodges | Entries (RSS) and Comments (RSS)

Your Index Web Directorywordpress logo
Close