Imagine a production line - say, building a car. Starting from the beginning, a long, defined sequence of operations takes place, and at the end a car comes out. At various points along the way, production stops and waits for input from a human being; other parts of the process can be carried out automatically.

Taking this as a metaphor (I'm not coding a literal production line), how would you structure something like this in object-oriented code? My specific situation is that I have an existing large method calling a lot of other large methods, all of which are peppered with UI interaction, which I want to refactor so I can call it from within a web service. I feel like my route to success is to work out how I could better code it if I were starting from scratch and then aim to evolve the architecture in that direction.

I could break it up into a sequence of method calls that represent steps along the way, but I want to encode the ordering too, since, e.g. calling A then B then C is correct but B then A then C is not. Also those methods would serve no other purpose than being parts of the whole, so it seems like I'm almost arbitrarily chopping the main method into pieces just to satisfy the desire for short methods.

I think a completely different approach is needed and I'd be grateful for some inspiration. It feels like maybe it's a state machine? But then again the ordering must be encoded somewhere.

Any ideas?

有帮助吗?

解决方案

Sounds like you want to design your system in a more functional manner, using a pipes & filters architecture.

  • decompose your large functions into smaller ones or smaller components which are responsible for processing one step

  • each component should have a well-defined input set and a well defined output sets, with ideally no side-effects except things like logging

  • "UI interactions" should be strictly separated from those components and injected into the processing components using interfaces or callbacks (so they can be exchanged by different technologies, replaced by mocks or automated non-UI reactions)

  • the order of steps is determined in some outer "assembling" method which instantiates the individual components and connects their input and output channels

  • especially for huge datasets, it may be a good idea to use streams or "lazy sequences" (like IEnumerable / yield in C#, or iterators in Python) for the input and output data of the components of the "production line"

This approach leads to components which can be reused very easily within a desktop UI, within a web services or run within some unit tests.

其他提示

Long running operations, especially those requiring user interaction, are nicely represented by state, state that is persisted perhaps rather than as objects in memory.

You might consider a message broker, or, a database, to establish persistent queues of things waiting for each specific human interaction required (e.g. approval) or awaiting resources, inventory, or other external events.

I would use OOP within the handlers, but not for persistence when waiting for events like UI.

Modeling the entirety within OOP seems like it could promote a monolithic design, using threads for long running concurrent jobs, and issues during outages, restarts, and upgrades — all of which are pitfalls.  I would consider persistence (stepping out of OOP at certain points) up front.

The production line metaphor itself may lead you a less object-oriented design.

Object-orientation is not about deconstructing a problem into steps needed to make some output happen. It is about deconstructing the problem into cooperating, but individual objects. This (i.e. OO) may be the completely new approach you are looking for.

State and ordering are technical things, that can ultimately be encoded in many different forms. For example if you create a good vocabulary (public classes and methods) of what is happening, it might encode the ordering in the type system. I.e. you can only call a method after you somehow create/generate/get another object.

Try to start with things that this process involves. Reports, Accounts, Amounts, you know... business things. Try to imagine methods on these things then and a chain of calls that would solve your problem.

HTH

Let's set aside the parallelisation of operations for now and keep it as a "chain" of operation like a manufacturing chain.

I would advise to read about the factory and builder pattern first.

Then my advise is to decouple the operations as much as possible.

Let's take the point of view of the user. The user/manufacturer only car to have the car build so let's start with buildCar(Model)

buildCar can then ask the different part of the line to do their job. We can have:

buildCar(Model) {
var frame = FrameFactory.BuildFrame(Model)
EngineFactory.BuildEngine(Model, frame)
WheelFactory.BuildEngine(Model, frame)
...
color = PromptUserForColor()
PaintFactory.Paint(frame, color)
return FinalTouch(frame)
}

Here I use the hypothesis that each Factory object can retrieve the proper information from just an ID. This can be achieved with something like a BluePrintDB injected to each factory that contains the parameters of each component for a specific models Example

EngineFactory.BuildEngine(Model, frame) {
 var parameters = BluePrintDB.GetEngineParameters(Model)
 return new Engine(parameters)
}

Each object/class as limited responsibility (SRP principle) even the object holding the buildCar method has only one responsibility (assembling the car) it is delegating details to other object.

You are looking for an Orchestrator pattern. The purpose of this pattern is to manage the order that things need to happen in. So in sticking with the car example you would call something like CarOrchestrator.Build() that would handle calling AddFrame then AddAxels and so on.

All the logic to do things would be in other objects, so that the business logic can be isolated and tested separately from managing the order, which can also be easily tested.

Looking just at your requirements alone, from an OO perspective, this is what I came up with (assuming "car" is actually an object that does things):

let car = Car()

Take care!

Edit: Okay. In order to avoid more downvotes, i'll expand on another possible answer.

assuming "production line" is an actual object which does things, another possible approach can be similar to the following:

let productionLine = LeanProductionLine()
productionLine.beginAssemblingCar()
productionLine.stop()

let progress = productionLine.reportProgress()
progress.displayIndicator()
progress.sendEmail()

Hope that helps!

许可以下: CC-BY-SA归因
scroll top