User Tools

Site Tools


moduleclass

Module Level I: Class

Classes are the lowest or most fine grained level of the module hierarchy. They are the immediate containers of functions.

Basic usage of classes (in object-oriented languages) just aggregates functions under a common name. In that way they work like a namespace.

class FileAccess {
  public static void Write(string filename, string text) {...}
  public static string Read(string filename) {...}
}
 
...
 
FileAccess.Write("greeting.txt", "Hello, Bruce!");

If the behaviour to be created by the functions becomes more elaborate, more function will be created, of which only a few are of interest to clients of the class. Classes allow to hide such details as well as any shared data behind an interface. Their purpose moves from aggregation to integration:

class FileAccess {
  public static void Write<T>(string filename, T data) {
    var serializedData = Serialize<T>(data);
    WriteSerialized(filename, serializedData);
  }
 
  private static byte[] Serialize<T>(T data) {...}
  private static void WriteSerialized(string filename, byte[] data) {...}
 
  public static T Read<T>(string filename) {...}
  ...
}

The interface in this example consist only of the public functions Write<T>() and Read<T>().

Static classes often are not viewed as “proper” classes and means of object-orientation. Flow-Design begs to differ. Even static functions in static classes have their place in clean code development. For one they are easier to test because they don't require instantiation.

But as soon as shared state enters the picture it's useful to use instance classes, i.e. classes from which object can be created:

class FileAccess<T> {
  private string _filename;
 
  public FileAccess(string filename) { _filename = filename; }
 
  public void Write(T data) {
    var serializedData = Serialize(data);
    WriteSerialized(serializedData);
  }
 
  private byte[] Serialize(T data) {...}
  private void WriteSerialized(byte[] data) {...}
 
  public static T Read<T>() {...}
  ...
}
 
...
 
class Person { ... }
 
var repo = new FileAccess<Person>("peter.txt");
var peter = repo.Read();
...
repo.Write(peter);

Shared state is one of the motivations for instance classes. Another is polymorphism. Because with objects receivers of messages (function calls) can be passed around. A client of a function can work with different targets:

void CheckForDoubles(FileAccess<Person>[] persons) {
...
}

And not even the concrete type of an object as a function host needs to known, as long as the object supports a function. In strongly typed languages this is achieved by abstracting from different implementations by extracting a common interface (abstraction by distillation)

interface IRepository<T> {
  void Write(T data);
  T Read();
}
 
class FileAccess<T> : IRepository<T> {
...
}
 
class DbAccess<T> : IRepository<T> {
...
}
 
...
 
void CheckForDoubles(IRepository<Person>[] persons) {
...
}

The explicit interface definition is a (syntactic) contract which can be implemented in different ways and which can be relied on by clients.

A common use case for such a contract is the application of the Dependency Inversion Principle (DIP) to increase testability where functional dependencies exist.

Summary

At the least classes reduce the number of pieces you have to juggle in your head from many functions to few(er) classes thereby increasing understandability.

Additionally they lower the coupling between those pieces by hiding implementation details behind interfaces. And they increase the flexibility by providing polymorphism.

moduleclass.txt · Last modified: 2018/07/21 17:31 by fdadmin