Let's say I am writing code to run a machine similar to a 3D printer and I have a routine like:
machine.moveTo(x1, y1);
machine.alignByCamera();
machine.heatUp();
machine.extrude(amount1);
machine.moveTo(x2, y2);
machine.extrude(amount2);
I want to add a new optional feature to keep track of process history. Let's say I want to keep temperature, shots from camera, coordinate values after alignment. So my new code looks like this:
machine.moveTo(x1, y1);
historyTracker.takeInitialPhoto();
historyTracker.recordLocationBeforeAligment(machine.x, machine.y);
machine.alignByCamera();
historyTracker.recordLocationAfterAlignment(machine.x, machine.y);
historyTracker.recordTemperatureBeforeHeatup(machine.temperature);
machine.heatUp();
historyTracker.recordTemperatureAfterHeatup(machine.temperature);
machine.extrude(amount1);
machine.moveTo(x2, y2);
machine.extrude(amount2);
historyTracker.takeFinalPhoto();
As you can see code becomes much uglier. I even haven't not made it an optional feature yet. Let's make it optional:
machine.moveTo(x1, y1);
if(historyEnabled)
historyTracker.takeInitialPhoto();
if(historyEnabled)
historyTracker.recordLocationBeforeAligment(machine.x, machine.y);
machine.alignByCamera();
if(historyEnabled)
historyTracker.recordLocationAfterAlignment(machine.x, machine.y);
if(historyEnabled)
historyTracker.recordTemperatureBeforeHeatup(machine.temperature);
machine.heatUp();
if(historyEnabled)
historyTracker.recordTemperatureAfterHeatup(machine.temperature);
machine.extrude(amount1);
machine.moveTo(x2, y2);
machine.extrude(amount2);
if(historyEnabled)
historyTracker.takeFinalPhoto();
So my question is, how can I decouple history tracking feature from the routine itself if it is possible. Otherwise how can I improve my code. Is there a better way(design pattern maybe) to approach this problem.
1 Answer 1
I'm going to make a working assumption that you are creating a kind of a passive history. A passive history allows the user to observe the progress. During troubleshooting, it helps to understand what was has happened. In other words, this is a passive log.
[Please comment if my assumption is incorrect. ]
In case of a passive history, there's nothing wrong with writing this history inside the machine control routine itself. The control routine doesn't need to know if history is enabled. The history tracker knows if the history is enabled.
If I were to do a quick refactoring of your last code listing, I would:
Move the logic needed for enabling history inside history tracker class. That way, the tracker's client code doesn't need to know or worry if the history is enabled or disabled. The history tracker would be the only place for that logic.
Move the calls to the history tracker inside of the machine control class.
Pass (inject) a reference to the history tracker as a constructor parameter into the machine control class.
This is not unlike using a logging framework, and perhaps you could use some existing logging framework for this purpose (and use that same framework for writing debug logs as well).
A decorator pattern can be alternative to putting calls to history tracker inside the control routines themselves.
Quick sketch of a logging decorator (in C#) :
public class HistoryMachineDecorator : MachineDecorator
{
private HistoryTracker m_historyTracker;
public HistoryMachineDecorator(IMachine decoree, HistoryTracker historyTracker) : base(decoree)
{
m_historyTracker = historyTracker;
}
public override void alignByCamera()
{
m_historyTracker.recordLocationBeforeAligment(m_decoree.x, m_decoree.y);
m_decoree.alignByCamera();
m_historyTracker.recordLocationAfterAlignment(m_decoree.x, m_decoree.y);
}
// more methods...
}
// The rest is for the sake of completeness.
public interface IMachine
{
void alignByCamera();
// more methods...
}
public class Machine : IMachine
{
public void alignByCamera()
{
// do the aligning
}
// more methods...
}
// abstract decorator class
public abstract class MachineDecorator : IMachine
{
protected IMachine m_decoree;
public MachineDecorator(IMachine decoree)
{
m_decoree = decoree;
}
public abstract void alignByCamera();
// more methods...
}
p.s.
A more interactive history is the one that allows the user to interact with old commands. He might re-run or undo them. If that's your case, look into command pattern.
-
Thank you so much! I really like the decorator pattern idea.yez– yez2019年04月05日 09:07:18 +00:00Commented Apr 5, 2019 at 9:07
Explore related questions
See similar questions with these tags.