Firstly, I'll try to describe the real world model of what we are trying to express in C# code.
We have a device called M100. It's purpose is to read\write data from\into cards (plastic or ULTRALIGHT cards such as MIFARE). It can be with one up to 3 antennas (the device read\write data through antenna). One of antennas is internal (so card can be put on the device surface and the device will be able to perform read\write operations), the other are external. Let's consider the usual situation. We have a point of service terminal. M100 reside inside the terminal and it has two external antennas wich are placed over a tract of cards dispenser. Cards dispenser is another device which is responsible for accepting and dispensing cards for performing read\write operations by M100. So, card dispenser takes the card from the user, move it to the read\write position and after that M100 can perform read\write operations. M100 can be equipped with only one (internal) antenna. So we have to main cases: with and without card dispensers.
We have one application that interacts with dispensers and the other uses M100 without card dispensers.
We developed M100Provider with it's interface IM100Provider. This is the low-level class which interacts with M100 directly, passes commands through COM-port.
We have a higher level-operations such as AuthorizeOperator, WriteTicket, ReadCard (all this operations require M100) so we developed M100Communicator class (maybe the name does not reflect it's intention at the time, but we will think about the name a bit later). M100Communicator is a singleton. Because we need to manipulate card dispensers to accept and dispense users cards M100Communicator interacts with CardDispensersManager class. We haven't decide yet wil it be composed into M100Communicator or will it be a singleton (it seems like it's a singleton).
All these operations are have to perform in a kind of a unified way:
- Block the current object.
- Check if there are some dispensers configured
- If they are - pass to them a command to open the tract for card acception.
- Pass to M100 a command to search a card through specified antennas.
- When the card was found, perform read\write operations.
- Dispense card back to the user.
Here are the problems we are faced:
- Considering that M100Communicator sometimes doesn't require CardDispensersManager we are forced to check it's state to null reference everywhere (and it will be quite strange to have a special case CardDispensersManager which will return meaningfull responses while there are no dispensers!).
- M100Communicator and CardDispensersManager have to be initialized from the start of the system so it's unclear how to update the configuration of CardDispensersManager and M100Communicator and how to communicate with CardDispensersManager directly when we want to close ports and stop communication with dispensers explicitly, because it will be very strange to expose dispensers API through M100Communicator. (yes we have the requirement to be able to access dispensers API from everywhere, so we think that CardsDispenserManager has to be a singleton).