On my commute to work earlier this week, I came across a copy of New York Times that another passenger had read and discarded on the seat across from me, and having nothing better to do, started to read it. In the Science section, I came across Natalie Angier's article "Tiny Specs of Misery, both Vile and Useful", where a section caught my eye. Talking about how viruses attack human cells, it says:
All viruses have at their core compact genetic instructions for making more viruses, some of the booklets written in DNA, others in the related nucleic language of RNA. Our cells have the means to read either code, whether they ought to or not. Encasing the terse viral genomes are capsids, protective coats constructed of interlocking protein modules and decorated with some sort of docking device, a pleat of just the right shape to infiltrate a particular cell. Rhinoviruses dock onto receptors projecting from the cells of our nasal passages, while hepatitis viruses are shaped to exploit portholes on liver cells.
What impressed me was the clarity of the analogy, and how closely it resembles the Visitor Pattern. An example of (programming) art imitating life, I guess.
To express this analogy in concrete terms, consider an interface called ICell to model the target human cells, and another called IVirus to model the viruses. The IVirus.canVisit() method is driven by the similarity in shape between the capsids and the docking ports on the target cells. The ICell.accept() method describes transformations to the cell when attacked by an instance of IVirus.
1 2 3 4 5 6 7 8 9 | // ICell.java
public interface ICell {
public void accept(IVirus virus);
}
// IVirus.java
public interface IVirus {
public boolean canVisit(ICell cell);
}
|
Assume that NasalPassageCell.java and LiverCell.java are two implementations of the ICell interface, shown below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // NasalPassageCell.java
public class NasalPassageCell implements ICell {
public void accept(IVirus virus) {
produceCommonCold(virus);
}
...
}
// LiverCell.java
public class LiverCell implements ICell {
public void accept(IVirus virus) {
produceStomachFlu(virus);
}
...
}
|
The above is obviously an oversimplification, since a particular cell implementation can react in different ways to different IVirus implementations, so perhaps that behavior can be modeled by delegating to private accept() methods, or using an else-if condition.
Similarly, assume that RhinoVirus.java and HepatitisVirus.java are two implementations of the IVirus interface. As before, a virus can probably visit different kinds of cells, so the canVisit() method is likely to be more complex than that shown below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // RhinoVirus.java
public class RhinoVirus implements IVirus {
public boolean canVisit(ICell cell) {
return (cell instanceof NasalPassageCell);
}
...
}
// HepatitisVirus.java
public class HepatitisVirus implements IVirus {
public boolean canVisit(ICell cell) {
return (cell instanceof LiverCell);
}
...
}
|
The coupling between the two classes of objects is effected using a CellVisitor class, shown below. A CellVisitor would actually be a virus (since it visits cells), so this logic could probably be put into a BaseVirus superclass instead of in a separate class. The CellVisitor class will define how the source object (IVirus) will traverse a collection of target objects (ICell), and is likely to be application-specific. In our case, it would probably look like this:
1 2 3 4 5 6 7 8 9 | public class CellVisitor {
public void processCells(IVirus virus, Collection<ICell> cells) {
for (ICell cell : cells) {
if (virus.canVisit(cell)) {
cell.accept(virus);
}
}
}
}
|
This pattern is quite powerful, both for viruses and code. As time passes, cells grow resistant to certain viruses, and viruses mutate into new viruses which attempt to overcome the cell's newly developed resistivity. However, as long as cells and viruses continue to conform to the same interface definition, the CellVisitor.processCells() method will continue to work, although it will produce different results as the inputs change.
In case of code, this pattern provides a way to loosely couple two classes of objects. As long as the interface remains unchanged, new implementations can be quickly added without concern that the behavior will change.
No comments:
Post a Comment
Comments are moderated to prevent spam.