Skip to content

Instantly share code, notes, and snippets.

@MirzaLeka
Created July 25, 2026 09:03
Show Gist options
  • Select an option

  • Save MirzaLeka/be557c58a1ed8e2b0fedcd0e0b9168d9 to your computer and use it in GitHub Desktop.

Select an option

Save MirzaLeka/be557c58a1ed8e2b0fedcd0e0b9168d9 to your computer and use it in GitHub Desktop.
Encapsulation, Abstraction, Inheritance, Immutability, Factory

Applying the Reply (Result) pattern into existing error-handling solution

The project uses the Error class to create new error response objects:

public class Error
{
		public int StatusCode { get; set; }
		public string Message { get; set; }

		public Error(int status, string msg)
		{
			StatusCode = status;
			Message = msg;
		}
}

The goal

The Error class uses business logic needed through the project. This leaves the gaps open for the accidental or purposeful (lazy) misuse. To keep domain invariants intact, create a wrapper (Reply) class that ensures that all errors follow the same structure and semantics.

The goal is to have only one error-handling solution via the Reply class. The Reply class is an imagination of the Result pattern (simplified for this example).

public class Reply
{
		public static Error ValidationError(string message)
		{
			return new Error(400, message);
		}

		internal static Error DataNotFoundError(string message)
		{
			return new Error(404, message);
		}
}

The example demonstrate how the Reply class creates instances of the Error class. So that means you can use (the new API) Reply to get the Error.

But still, it doesn't stop anyone from declaring instnace of both Reply and Error anywhere in the project.

Q: How to hide Error class from the outside and just use the Reply class?

IError interface

Create a public IError interface accessible from anywhere.

	public interface IError
	{
		int StatusCode { get; }
		string Message { get; }
	}

The interface shares the same properties as the Error class, so let's make the Error class implement it:

public class Error : IError

Private class

Make the Error class private and move it within the Reply class:

	public class Reply
	{
		public static Error ValidationError(string message)
		{
			return new Error(400, message);
		}

		internal static Error DataNotFoundError(string message)
		{
			return new Error(404, message);
		}

		private class Error : IError
		{
			public int StatusCode { get; set; }
			public string Message { get; set; }

			public Error(int status, string msg)
			{
				StatusCode = status;
				Message = msg;
			}
		}
	}

The compiler complains. The Error class is private, it cannot be returned from the Reply class due to the protection level:

		public static Error ValidationError(string message) // ❌
		{
			return new Error(400, message);
		}

Replace Error with IError

The IError interface uses the same properties as the base class and it's also public. Replace Error with IError as the retrn type:

public class Reply
{
   	public static IError ValidationError(string message)
   	{
   		return new Error(400, message);
   	}

   	internal static IError DataNotFoundError(string message)
   	{
   		return new Error(404, message);
   	}
//...
}

Because you can't create instances of the interface (IError) this won't cause any problems.

Quick recap

Head over to your main file (Program.cs) and create instnaces of Reply and Error:

			var err = Reply.ValidationError("Something went wrong");
			var err2 = new Error();  // ❌

You'll notice that compiler complains about the Error class as expected.

Immutability

Currently, the Error class uses set; on properties, which means anyone inside the assembly can mutate the error after creation. Fix that with init;:

			public int StatusCode { get; init; }
			public string Message { get; init; }

No Inheritance

Seal the Error to stop anyone from creating custom Error classes that derive from the Error class:

private sealed class Error : IError {...}

Internal factory methods

Abstract the implementation details by exposing custom Error methods to the outside. Each method will produce a different Error, preventing the Reply class to make modifications.

		private sealed class Error : IError
		{
			public int StatusCode { get; init; }
			public string Message { get; init; }
      
			private Error() { }      

			internal static IError BadRequest(string message)
			{
				return new Error { StatusCode = 400, Message = message };
			}

			internal static IError NotFound(string message)
			{
				return new Error { StatusCode = 404, Message = message };
			}
		}
	}

That's why I also made the Error constructor private.

Use factory methods within the Reply

The private constructor is causing errors in the Reply class as expected:

		public static IError ValidationError(string message)
		{
			return new Error { }  // ❌
		}

Replace Error instances with the Error factory methods. Also add a private constructor inside Reply so it cannot be instantiated either:

	public class Reply
	{
		private Reply() { }

		public static IError ValidationError(string message)
		{
			return Error.BadRequest(message);
		}

		internal static IError DataNotFoundError(string message)
		{
			return Error.NotFound(message);
		}

		private sealed class Error : IError
		{
			public int StatusCode { get; init; }
			public string Message { get; init; }

			private Error() { }

			internal static IError BadRequest(string message)
			{
				return new Error { StatusCode = 400, Message = message };
			}

			internal static IError NotFound(string message)
			{
				return new Error { StatusCode = 404, Message = message };
			}
		}
	}

Final test

You can create new errors only using the methods that the Reply class exposes:

			var err = Reply.ValidationError("Something went wrong");

Summary

  • The Error class is completely sealed off.
  • The Reply class is the key. The Reply exposes the Error class via IError.
  • No one can inherit, mutate or create instances of Error beyond using the factory methods that class exposes.
@MirzaLeka

Copy link
Copy Markdown
Author

The alternative solution would be to move the Error, IError and Reply into a different project (class library).

  • The IError would remain public.
  • The Error would be internal in it's own file:
internal class Error : IError
{
            public int StatusCode { get; set; }
            public string Message { get; set; }
}
  • The Result would be public and return IError:
	public class Reply
	{
		private Reply() { }

		public static IError ValidationError(string message)
		{
			return Error.BadRequest(message);
		}

		internal static IError DataNotFoundError(string message)
		{
			return Error.NotFound(message);
		}
	}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment