Reminder: If you have tips, please remember to put the reason objectively, such as "having two distinct SetInt()
functions in the same file violates reader expectations that they'll be overloads, and stymies the ability to find the right function with ctrl+F."
Problem:
Occasionally, low level/dangerous APIs are needed. They should be hidden, but they can't be private. For example, functions to manipulate an additional (not loaded) save data, rename/delete the underlying save data config files on disk, or disallow any future saves from being written to disk. How do I keep the API clean, so normal classes don't see these ancillary methods?
In C++, these ancillary functions would be private, and the classes that needed access would be friend classes. In C#, my first thought was to use this:
Pseudo-Facade pattern, hiding ancillary methods:
// The Save.Foo() functions are used all the time. Concise/simple API needed.
public static class Save
{
public static void SetInt(string key, int value)
{
Save_Implementation.SetInt(currentSaveData, key, value);
}
}
public static class Save_Implementation // ancillary or dangerous APIs
{
public static void SetInt(object data, string key, int value) { /* ... */ }
public static void StopAllSaves() { /* ... */ }
public static string GetCurrentFilename() { /* ... */ }
}
This works, but I should related the classes to better clarify that they're part of the same thing. Subclass? Can't, it's static. Namespace? Can't, we need to type these function names with brutal frequency. Nested class? Yes, please.
Nested class:
public static class Save
{
public static void SetInt(string key, int value)
{
Impl.SetInt(currentSaveData key, value);
}
public static class Impl // ancillary or dangerous APIs
{
public static void SetInt(object data, string key, int value) { /* ... */ }
public static void StopAllSaves() { /* ... */ }
public static string GetCurrentFilename() { /* ... */ }
}
}
That's better, but messier than I'd prefer, since there are duplicated methods in the same file. (I.e., SetInt()
calls Impl.SetInt(currentSaveData)
.) Next I tried splitting it up:
Partial classes, with ancillary stuff in a different file:
Save.cs:
public static partial class Save
{
public static void SetInt(string key, int value)
{
Impl.SetInt(currentSaveData, key, value);
}
public static partial class Impl // ancillary or dangerous APIs
{
}
}
SaveImplementation.cs:
public static partial class Save
{
public static partial class Impl // ancillary or dangerous APIs
{
public static void SetInt(object data, string key, int value) { /* ... */ }
public static void StopAllSaves() { /* ... */ }
public static string GetCurrentFilename() { /* ... */ }
}
}
This works, but the IDE isn't smart enough to open the right file when I jump to the definition of Save or Save.Impl. Is there a perfect way to organize this, or will it always be a trade-off?
Edit: Save
is a drop-in replacement for the game engine's default save functionality, so it's ideal for it to clone the existing API (and be static).