Re: design problem



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



.



Relevant Pages

  • Re: design problem
    ... In your example IPayDataCollector interface knows about hourly payment ... Strategies also know about IPayDataCollector interface so we have cyclic ... This exposes some details about strategy in Employee: ... void CollectHourlyPayedData(double hours, decimal hourlyWage); ...
    (comp.object)
  • Re: design problem
    ... Yes, however, I made the Form implement the collector interface only for the sake of clarity. ... I could make the Factory on the GUI side implement the collector and create appropriate view. ... public void SetEmployee(Employee employee) ... but it shows only the fact that there is some strategy for payment not which one was actually used. ...
    (comp.object)
  • Re: Downcasting - whats the problem?
    ... In order to regain that static type information later, ... The employee holds an object that describes it's pay classification. ... One represents a salaried payment, ... HourlyClassification holds on to a set of time cards. ...
    (comp.object)
  • Re: A funny thing happened...
    ... one only shows what the payment was for and not who made the purchase on ... reimbursing against a receipt, the "proper way" is to 'look through' who ... For example an employee may buy stamps, ... providing the employee with either earnings or expenses because the employee ...
    (uk.rec.scouting)
  • Sufficient reason to de-normalize a field?
    ... I've got a great database design. ... I have a form where I save payment information and the payee might ... of the payee when it is an employee. ...
    (microsoft.public.vb.database)