Re: design problem
- From: "Sergey Alpaev" <s_alpaev@xxxxxxx>
- Date: Fri, 6 Oct 2006 09:47:42 +0300
1) Explicit strategy type checking on the Employee (or its strategy)
2) Revealing the existence of strategies, both interface and each
implementation
3) ... and the fact that employee has only one payment model
Items N2 and N3 are probably the worst problems. I missed the requirement
that you need to have many payment models active at the same time for the
the same Employee instance
Regarding N1 I believe that this type checking is done within strategy view
factory which is anyway specific to strategy since each instance creates
only one specific type of strategy (Abstract Factory, not FactoryMethod
pattern). So the factory needs only to check if the given strategy is from
the same "strategy domain" as the factory itself (i.e. if the factory is for
hourly views it can be given only hourly strategies). Yes it is a problem
:-)
Now the question - how do you plan to hide existence of strategies? You need
somehow to configure Employee object with strategy instance, right? The
solutions I see is either giving the strategy as parameter in some method or
making Employee asking some factory to create strategy which in turn
requires configuring Employee object with factory instance.... So anyway
there is something which knows about strategies outside the Employee
object... unless it asks some singleton or global registry for strategies...
To me the type of payment does not seem implementation detail of Employee
but rather a kind of business logic for employee. If we discussed, let say,
the way the Employee persisted then it probably can be considered an
implementation detail since regular "business" user (as opposed to
administator the system) does not care how it is persisted. But he probably
does care about how the employee is paid. Someone (let say HR Manager) will
need to move employees from one payment model to another, another user (let
say Employee) will need to be notified if payment model has been changed
unexpectedly :-)
This does not require exposing actual implementation of payment calculation
but probably calls for exposing the type of the calculation to the external
world (some abstract name of the type, let say with name="hourly payed" and
description="Hourly payment according to <> form....").
Some kind of :
class Employee
{
PaymentModel getPaymentModel(string jobType /*is this how you envision
many payment models at the same time?*/);
void ChangePaymentModel(PaymentModel model, string jobType);
bool WillBeSatisfiedWithPaymentModel?(PaymentModel model) throws
UpdateResumeException :-)
}
class PaymentModel
{
string id;
string descrtiption;
}
Then payment model instance can be used to identify actual implementations
of calculation strategy, presentation strategy, etc.
But this looks like extending the scope of the project.
So if you need to explode your scope just post to comp.object... :-)
"Sasa" <sasa555@xxxxxxxxx> wrote in message
news:eg3gbh$kb0$1@xxxxxxxxxxxxxxxxx
Sergey Alpaev wrote:
In your example IPayDataCollector interface knows about hourly payment
strategy (CollectHourlyPayedData method ) and about salaried payment
strategy (CollectSalaryData method). Therefore it is coupled to these two
strategies.
Not really, it knows about the primitive data but not about the specific
strategies.
But granted, I just broke it down to primitives (to avoid exposing
strategies). However, this is a true consequence of the Visitor pattern.
Strategies also know about IPayDataCollector interface so we have cyclic
dependency.
In this case not (see above), in general Visitor yes.
Also there is a dependency between strategies through this interface:
IPaymentStrategy --- > IPayDataCollector,
SalariedPaymentStrategy -->IPayDataCollector and
HourlyPaymentStrategy --- > IPayDataCollector.
This is just repeating, that strategies also know about IPayDataCollector
interface.
Concequences are following (decide if it is a problem or not in your
case):
1. When new strategy is added we need to touch IPayDataCollector (to add
new collecting method - Collect<strategyName>Data) and change
True.
EmployeePayDataForm object. Since you need to display new strategy you
will need to change the Form anyway, but that Form will be as large as
many strategies you have.
Yes, however, I made the Form implement the collector interface only for
the sake of clarity. In a more general example, I could make the Factory
on the GUI side implement the collector and create appropriate view.
So changing such a large class whenever you need to touch strategy may be
a problem.
It can be broken down if the Form gets too complex (see above). If you
want, I can try to make some sketch.
Another problem is that showing strategy on the screen may be a complex
thing because the GUI may be complex with many controls. It may quickly
become unmanageable since all the GUI code for all strategies is in fact
placed in one class. For example, you will need to distinguish between
controls for different strategies (they all are members of Form object so
there is no separation for them except naming convention), make sure they
do not interfere with each other (you did not add control for one
strategy into container for another one) etc etc.
We need to change large Form object which knows about all strategies
instead of changing only the code relevant to strategy.
So adding new strategy will actually require retesting GUI for all others
in general case since they all are in one class.
All of this can be further subdivided on the GUI side when it gets too
complex. The single disadvantage I see, which cannot be avoided, is that
adding new interface requires changes in the collector. This basically
means that every single strategy must be recompiled, as well as every user
of the collector (as hinted above, it doesn't necessarilly need to mean
the entire Form).
2. We cannot have one compilation module (assembly, let say in C#) which
contains only abstract interfaces for strategies (IPaymentStrategy and
others if needed) and never changes and extension modules with
strategies. We will need to put all this in one assembly otherwise there
would be a cyclic dependency between assemblies imposed by cyclic
dependency between classes.
As long as you hide behind the primitives, or extract strategy specific
data to external classes/structures, you can avoid this somewhat. The fact
is that the collector (and hence all strategies) need to be changed when:
a) new strategy is added
b) strategy specific data gets changed (new data added, data type changed,
etc)
but not when
c) implementation of strategy changes.
[snip]
class EmployeePayDataForm : Form
{
public void SetEmployee(Employee employee)
{
IStrategyView view =
employeePaymentStrategyViewFactory.createView(employee.getPaymentStrategy);
If I'm not misreading this, in order for this to work, factory must do
some runtime type checking, or some similar mechanism. Right?
this.Controls.Add(view);
view.Location = ...
view.update();
}
}
class HourlyPayedStrategyView: PaymentStrategyView
{
HourlyPayedStrategyView(Form parent, HourlyPaymentStategy strategy)
{
}
void update()
{
// populate controls with data from strategy
}
}
class HourlyPaymentStrategyViewFactory :
IEmployeePaymentStrategyViewFactory
{
StrategyView createView()
{
return new
HourlyPayedStrategyView((HourlyPayedStrategy)strategy); // the cast I was
talking about
In addition to down cast, this also means that you are exposing specific
strategy (and not just the interface) to the outer world.
This exposes some details about strategy in Employee:
class Employee
{
IPaymentStategy getStrategy();
}
but it shows only the fact that there is some strategy for payment not
which one was actually used. And you anyway need to track how employee
payment is
This also shows (and couples the view with) the fact that Employee has
only one strategy.
calculated, right? So there is no way to hide the fact that employees
have different strategies otherwise employees will ask questions .... :-)
Now interface IPaymentStrategy and class PaymentStrategyView are part of
common framework which does not need to change when new strategies are
added. EmployeePayDataForm also does not need to change. We can extend
system with new strategies without changing old code just by adding new
classes - new implementation of IPaymentStrategy and
PaymentStrategyView-derived classes.
To do that we also will need to set new factory instance for
EmployeePayDataForm object but this is configuration issue.
Granted, it is relatively easy to add new strategy:
1) Add the strategy
2) Add the view
3) Adjust the factory
But the trade offs are the ones I listed above:
1) Explicit strategy type checking on the Employee (or its strategy)
2) Revealing the existence of strategies, both interface and each
implementation
3) ... and the fact that employee has only one payment model
The collector solution doesn't have these downsides, but there is still
the fact that when I add new strategy, collector interface must be changed
and therefore each of its users. In addition, each strategy must still be
recompiled.
Sasa
.
- Follow-Ups:
- Re: design problem
- From: Sasa
- Re: design problem
- References:
- design problem
- From: Sasa
- Re: design problem
- From: Sasa
- Re: design problem
- From: Sergey Alpaev
- Re: design problem
- From: Sasa
- Re: design problem
- From: Sergey Alpaev
- Re: design problem
- From: Sasa
- design problem
- Prev by Date: Re: design problem
- Next by Date: Re: Archetypes vs Stereotypes
- Previous by thread: Re: design problem
- Next by thread: Re: design problem
- Index(es):
Relevant Pages
|
|