Note: this post is about Object Oriented design, not vacuum cleaners or AI.
I’ve recently got the AIMA book, which is probably the most fundamental textbook on AI. Today I set out to complete excercises for Chapter 2, “Intelligent Agents.”
Majority of the excercises are about designing an intellectual vacuum cleaner in various environments under different performance metrics. Initially seemed trivial, they proved to make me strain my software architecture skills.
The task is to program an agent (vacuum cleaner, VC for short) and evironment, defined as follows. The environment consists of only two locations, A and B. There may be garbage in either of locations. The VC can perform four actions:
- Left (go from B to A if at B, else no effect),
- Right (go from A to B if at A, else no effect),
- Suck (clean the location if dirty),
- NoOp (no action).
The VC should be implemented as function called by, say, Environment, which returns the action to be performed next. The VC’s sensors allow it to know where it is and if there is any garbage. Actions, btw, are performed by VC’s actuators.
First excercise is to implement the VC agent acting according to folowing logic:
public Action act(Sensor sensor) {
if (sensor.isDirty()) {
return Action.Suck;
} else if (sensor.getPosition() == Environment.POS_RIGHT) {
return Action.Right;
} else if (sensor.getPosition() == Environment.POS_RIGHT) {
return Action.Left;
}
}
Seems trivial, isn’t it?
In fact, it isn’t, provided that you must implement the thing in fully modular way, where you could change agent function or performance metric, or actuators or sensors (say, to model a broken sensor) independently.
I’ll tell you how did I solve this problem.
First of all, there be VacuumCleaner, Environment and PerfMeter objects. Environment is responsible for running the VC and for letting PerfMeter collect its data:
public void Environment.run(
int forTime,
VacuumCleaner agent,
Actuator actuator,
Sensor sensor,
PerfMeter meter) {
meter.reset(this);
for (int i = 0; i < forTime; i++) {
sensor.sense(this);
Action action = agent.act(sensor);
actuator.performAction(action, this);
meter.examineEnvironment(i, this);
examineEachPosition(meter);
}
}
Therefore PerfMeter acts like Visitor from Design Patterns, and I think a can refer to agent as ‘stateful strategy’… (Haven’t I said that a strategy must be stateless — to be called from several places simultaneously?)
Sensor is an intermediary object that is takes data from Environment and passes it to the agent, probably disfiguring that data to simulate broken mechanism.
Actuator is another intermediary object which effects agent’s will. Therefore it can prevent our VC from doing something, like Sensor can prevent from knowing something.
This approach worked rather well for environment with two locations, but what I am to do if I also want to create an environment representing two-dimensional surface? I obviously want to subclass the Environment because I have to store location data differently. And how to refer to positions? I used an int to denote 1D position, but now I want to pass two numbers — x and y coordinates of the VC.
The solution comes from the book Refactoring by Martin Fowler. We may use ‘Replace Data Value with Object’ and index our location maps with Position object, and this won’t break our old code if we introduce two constant positions, Left and Right (or A and B).
The code works but still that code ‘smells’ because sometimes subclasses often owerride too many methods of their ancestors, it would be better to extract some interfaces… But it just doesn’t matter.


