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?
Recommened design pattern to handle multiple compression algorithms for a class hierarchy
c#;design;design patterns;object oriented;architecture
Visitor Pattern: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 Base1Compresing Base1(a) using algorithm Y optimized for Base1Compresing Base2(b) using algorithm X optimized for Base2Compresing Base2(b) using algorithm Y optimized for Base2