Search code examples
c#oopdesign-patternsencapsulation

How to design objects to be used by different programs?


Let's suppose:

  • We have a bunch of Shape classes
  • Each shape has a public Draw() method and knows how to draw itself
  • We have a program whose only task is to load and display the shapes from file, not change them
  • We have a tool used for editing Shape classes visually, meaning a Shape class will be drawn to the screen, the user can select it and edit its data
  • The user can serialize the shape to a file and the program that displays the shapes should be able to deserialize the shape to look the same as the user saved it from the tool
  • Shapes contain non-public fields that determine the behavior of public methods, e.g. a Color field that tells the Draw() method what color to draw the shape in
  • It is fields like this that can be edited by the tool, but should not be visible to the program that displays the shapes

Therefore, the question is how to implement the shapes?

  • Having them implemented in a shared library that could be used by both programs would break encapsulation, but would have a single implementation.
  • Having one implementation for the editor, and another implementation for the program that draws the shapes, both implementations should have standardized de/serialization (same members) so they can be de/serialized properly. This fixes the encapsulation issue but we would have to maintain two implementations.

How should I design those shape objects, if I don't want the drawing program to know they can be edited at all?

Currently I am leaning towards the second option. Is there a third option I'm not seeing? What would you advice and why?


Solution

  • If you're going to be exchanging files between programs, or maybe storing them for a long time and loading them later after the program has changed, then you don't just use the default serialization of your working objects, which writes out private fields and breaks when you change them.

    In these cases, the data format is part of the objects' interface. It needs to be designed and specified.

    Designing a data format is a different job from designing a method calling interface. Your primary considerations are usually things like:

    • Backwards compatibility: How are you going to ensure that the data you write now will be usable by future programs? This is usually handled by a versioning scheme.

    • Forwards compatibility: How are you going to ensure that data written by future programs will be as usable as possible by Today's programs? This is usually handled by an extension mechanism.

    • Language independence: Do you need to make sure that programs to process the data are easy/convenient to write in other programming languages? This usually means you don't rely on complicated serialization formats that are defined by your language or libraries.

    • Text, binary, or in between?: Textual formats (often based on JSON, YAML or XML) are convenient for debugging and easy to document. Binary formats are more compact. Sometimes you can use a middle ground. MS Office files, for example, are text files packaged up in a .zip archive.