C# Design Patterns – Handling Multiple Compression Algorithms in Class Hierarchy

Architecturecdesigndesign-patternsobject-oriented

For all you OOD experts. What would be the recommended way to model the following scenario?

I have a certain class hierarchy similar to the following one:

class Base {
   ...
}

class Derived1 : Base {
   ...
}

class Derived2 : Base {
   ...
}
...

Next, I would like to implement different compression/decompression engines for this hierarchy. (I already have code for several strategies that best handle different cases, like file compression, network stream compression, legacy system compression, etc.)

I would like the compression strategy to be pluggable and chosen at runtime, however I'm not sure how to handle the class hierarchy. Currently I have a tighly-coupled design that looks like this:

interface ICompressor {
   byte[] Compress(Base instance);
}

class Strategy1Compressor : ICompressor {
   byte[] Compress(Base instance) {

      // Common compression guts for Base class
      ...
      //

      if( instance is Derived1 ) {
         // Compression guts for Derived1 class 
      }
      if( instance is Derived2 ) {
         // Compression guts for Derived2 class
      }

      // Additional compression logic to handle other class derivations
      ...
   }

}

As it is, whenever I add a new derived class inheriting from Base, I would have to modify all compression strategies to take into account this new class. Is there a design pattern that allows me to decouple this, and allow me to easily introduce more classes to the Base hierarchy and/or additional compression strategies?

Best Answer

Visitor Pattern:

enter image description here

Note that the code contains no if-then-else or switch-case structures to select the appropiate compressor. It's not needed since the visitor patterns allow for the appropiate dispatch.

public interface IBase {
    void accept(ICompressorVisitor e);
    void setName(String s);
    String getName();
}

public class Base1 implements IBase {
    private String name="";
    @Override
    public void accept(ICompressorVisitor e) {
        e.visit(this);      
    }

    @Override
    public void setName(String s) {
        this.name = s;      
    }

    @Override
    public String getName() {
        return "Base1("+this.name+")";
    }

}

public class Base2 implements IBase  {
    private String name="";
    @Override
    public void accept(ICompressorVisitor e) {
        e.visit(this);      
    }

    @Override
    public void setName(String s) {
        this.name = s;      
    }

    @Override
    public String getName() {
        return "Base2("+this.name+")";
    }
}

public interface ICompressorVisitor {
    void visit(Base1 base);
    void visit(Base2 base);
}

public class CompressorX implements ICompressorVisitor {

    @Override
    public void visit(Base1 base) {
        System.out.println("Compressing "+base.getName()+" using algorithm X optimized for Base1");

    }

    @Override
    public void visit(Base2 base) {
        System.out.println("Compressing "+base.getName()+" using algorithm X optimized for Base2");
    }

}

public class CompressorY implements ICompressorVisitor {

    @Override
    public void visit(Base1 base) {
        System.out.println("Compressing "+base.getName()+" using algorithm Y optimized for Base1");

    }

    @Override
    public void visit(Base2 base) {
        System.out.println("Compressing "+base.getName()+" using algorithm Y optimized for Base2");

    }

}

public class Test {

    public static void main(String[] args) {
        Base1 b1 = new Base1();
        b1.setName("a");
        Base2 b2 = new Base2();
        b2.setName("b");

        CompressorX x = new CompressorX();
        CompressorY y = new CompressorY();

        b1.accept(x);
        b1.accept(y);

        b2.accept(x);
        b2.accept(y);

    }

}

Output of test:

Compresing Base1(a) using algorithm X optimized for Base1
Compresing Base1(a) using algorithm Y optimized for Base1
Compresing Base2(b) using algorithm X optimized for Base2
Compresing Base2(b) using algorithm Y optimized for Base2
Related Topic