Search code examples
c#compositesealed

Composite pattern of sealed class that has no interface


Let's say that I am using a library that I have no control over whatsoever. This library exposes service that requires argument of certain class. Class is marked as sealed and has no interface.

tl;dr: How can I reimplement sealed class as interface?

Code example:

using System;

namespace IDontHaveControlOverThis
{
    // Note no interface and the class is being sealed
    public sealed class ArgumentClass
    {
        public String AnyCall() => "ArgumentClass::AnyCall";
    }

    public sealed class ServiceClass
    {
        public String ServiceCall(ArgumentClass argument) => $"ServiceClass::ServiceCall({argument.AnyCall()})";
    }
}

namespace MyCode
{
    // Composite pattern, basically I need: "is a ArgumentClass"
    // Obviously doesn't work - can't extend from sealed class
    public class MyArgumentClass : IDontHaveControlOverThis.ArgumentClass
    {
        private IDontHaveControlOverThis.ArgumentClass arg = new IDontHaveControlOverThis.ArgumentClass();

        public String AnyCall() => $"MyArgumentCLass::AnyCall({arg.AnyCall()})";
    }
}

public class Program
{
    public static void Main()
    {
        // I don't have control over this
        IDontHaveControlOverThis.ServiceClass service = new IDontHaveControlOverThis.ServiceClass();


        //This obviously works
        IDontHaveControlOverThis.ArgumentClass arg = new IDontHaveControlOverThis.ArgumentClass();
        Console.WriteLine($"Result: {service.ServiceCall(arg)}");

        // How to make this work?
        IDontHaveControlOverThis.ArgumentClass myArg = new MyCode.MyArgumentClass();
        Console.WriteLine($"Result: {service.ServiceCall(myArg)}");
    }
}

Solution

  • Based on the code sample you show, the answer is you can't. You need to be able to modify the behavior of IDontHaveControlOverThis.ArgumentClass, by setting a property, or creating a new instance with different constructor parameters in order to modify the servicecall. (It now always returns the same string, so the servicecall is always the same)

    If you are able to modify the behavior of the ArgumentClass by setting properties. You could create wrappers for the sealed classes in your own code, and use that throughout your codebase.

    public class MyArgumentClass
    {
            // TODO: Set this to a useful value of ArgumentClass.
        internal IDontHaveControlOverThis.ArgumentClass InnerArgumentClass { get; }
        public virtual string AnyCall() => "???";
    }
    
    public class MyServiceClass
    {
        private IDontHaveControlOverThis.ServiceClass innerServiceClass
                = new IDontHaveControlOverThis.ServiceClass();
    
        public virtual string ServiceCall(MyArgumentClass argument)
        {
            return innerServiceClass.ServiceCall(argument.InnerArgumentClass);
        }
    }
    

    or

    public class MyArgumentClass
    {
        public virtual string AnyCall() => "???";
    }
    
    public class MyServiceClass
    {
        private IDontHaveControlOverThis.ServiceClass innerServiceClass
                = new IDontHaveControlOverThis.ServiceClass();
    
        public string ServiceCall(MyArgumentClass argument)
        {
            var serviceArgument = Convert(argument);
            return innerServiceClass.ServiceCall(serviceArgument);
        }
    
        private IDontHaveControlOverThis.ArgumentClass Convert(MyArgumentClass argument)
        {
            // TODO: implement.
        }
    }