1

Intent

Packages should be designed to perform a single function well.

Ideally this means that they should be highly modular and different packages should be able to be 'plugged-in' simply by ensuring that they share a common interface.

However, I'm unsure how to do this without creating some kind of dependency between the packages or creating ugly 'integration classes' or a 'common interfaces' package.

An Example Concept

I don't want to get too bogged down in domain-level symantics, so I'll use the simple, though slightly contrived, example of an event logger.

Package A

Does XYZ and allows for a custom logging object to be provided.

To ensure that the logger is valid, Package A contains a PackageA\Log interface. When a logging object is provided to the classX constructor, it throws an exception if the logger object does not implement PackageA\Log.

Package B

Does ABC and allows for a custom logging object to be provided.

To ensure that the logger is valid, Package B contains a PackageB\Log interface. When a logging object is provided to the classA constructor, it throws an exception if the logger object does not implement PackageB\Log.

Package C

A unified logger that provides logging for Package A and Package B... et al.

Contains Log object that is built to provide a generic logger for all packages in the application.

Current Sub-optimal Ideas:

1. Implement neither of the interfaces in the class PackageC\Log but extend the class for each interface variation (e.g. PackageC\Loggers\PackageA extends PackageC\Log).

Problem: Requires more maintenance and means that PackageC will have to be modified for every new package that it interfaces with.

2. Implement the interfaces PackageA\Log and PackageB\Log directly in the class PackageC\Log.

Problem: If PackageC is re-used in another project, errors will be thrown if either PackageA or PackageB are not present.

3. Create a Common Interfaces package and have all packages implement/require those interfaces for their public interfaces.

Problem: Massively impractical, would only work for integrating packages that you created; no third party interaction.

Question

How can PackageC\Log fulfill the requirements of both PackageA\ClassX and PackageB\ClassA?

In reality this question is usually more complex since the interfaces required by PackageA and PackageB are probably not the same. So is option 1 (defined above) the only way to solve this? i.e. the package that implements the interface has to write integration classes?

  • 1
    I think you named all the options. And I think this is "pick your poison" situation. All of the solutions resolve the issue, but each of them has it's problems. – Euphoric Feb 25 '16 at 14:22
  • @Euphoric As I feared; I certainly couldn't think of any other options. Still, it was worth a shot to see if there were any other solutions. – Marvin Feb 25 '16 at 14:25
  • Does coupling/decoupling apply to packages also? Are we using the clasical meaning of "package" or are we using "package" to signify something different? – Tulains Córdova Feb 25 '16 at 14:52
  • @user61852 Yes, classical meaning of package. e.g. a PHP package imported via composer, or a JS package through bower, etc. – Marvin Feb 25 '16 at 15:02
  • I thought packages were namespaces/folders. – Tulains Córdova Feb 26 '16 at 13:22
1

Since actual operations for a logging component will be pretty much the same for any other component/class/... that uses it, it makes sense to only offer one interface from Package C.

If Package A and/or Package B require other interfaces for their logging to work, it would make sense to use the Adapter design pattern to connect the interfaces of PackageA\Log to PackageC\Log and PackageB\Log to PackageC\Log.

The interface PackageC\Log remains generic, while predefined logging interfaces can be connected to it.

  • I've not used the adapter pattern before so please indulge me. If I've understood, I would use PackageC\Loggers\AdapterA to imitate PackageA\Log and map those method(parameters) calls to the correct method(parameters) on PackageC\Log? If so, is the adapter pattern essentially a formalised version of option 1 in my question? – Marvin Feb 25 '16 at 16:35
  • You would not insert an adapter inside any of these packages. An adapter is a piece of code (a component/class/...) that glues two different interfaces with the same intent together, without altering those interfaces (or the package/module/component/class/... it belongs to.) It may be a simple piece of code that simply takes the arguments from PackageA\Log and then calls the coreect interface member in PackageC\Log. It could also adjust the parameters if needed (like typeconversions, inserting default values, etc.) – Miguel van de Laar Feb 25 '16 at 16:43
  • So all of the packages are unaware of the adapter's existence; the adapter is created by the implementing application, like a specialised piece of wiring logic? – Marvin Feb 25 '16 at 16:54
  • Exactly. It is part of the plumbing and orchestration of the application. – Miguel van de Laar Feb 25 '16 at 16:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.