Re: Concept-oriented programming



H. S. Lahman schrieb:
To use one of your paper's examples, suppose one has a subsystem for managing a bank's accounts and the actual account data is stored in an RDB. Within the subsystem CoP will hide the funky details of creating SQL queries and parsing datasets to access something like Account.balance. So far that is a very good idea because at the level of abstraction that the bank wants to manage accounts, one does not want to think about those mundane details. But let's contrast two different approaches for separating those concerns.

The standard OOA/D approach is to encapsulate the database access mechanisms in a separate subsystem that understands the particular storage paradigm. The subsystem will have an interface with methods like getAccountData (accountNo). When an Account object needs to be created in the account management subsystem a factory object will invoke getAccountData and instantiate the object with the returned data.

As I understand CoP, one would create an Account reference with the various getBalance and whatnot accessors that the account management subsystem would use. One would also provide an implementation of an Account object that would know how to get that data from the database. That account object would be defined and implemented within the account management subsystem. [As a variation I suppose the reference could access the DB and instantiate the Account object a la the Singleton pattern. The subsequent accesses would be just passed through.]

My issue comes into play when one decides one wants to switch the database from an RDB to an OODB. Now one has a completely different paradigm for persistence access to deal with. In the OOA/D approach one would replace the database access subsystem. In doing so one does not touch the account management logic at all; it continues to invoke the same getAccountData interface. IOW, there is simply no way to break the existing account management solution. The only way one can break the overall application is by screwing up what data is returned through the getAccountData interface and that is isolated to the new subsystem that accesses the OODB (whose DbC contracts for getAccountData are exactly the same).

In contrast, in the CoP approach one has to provide a new implementation of the Account reference and Account object. Since those implementations exist within the account management subsystem there is /always/ a risk of breaking the account management logic, regardless of how logically isolated they are. Any time one touches a subsystem's implementation one can break it and the lore of software development is full of examples of things that could not possibly go wrong that did.

However, the potential problems don't end there. Accessing persistence is nontrivial and there are other issues that have nothing to do with the problem of managing accounts. Things like query optimization, caching, deadlocks, and whatnot represent issues that may need to be optimized /across/ elements like tables. They would almost certainly need to be optimized across accessors like getBalance (e.g., the DBA is going to be generating a lot of obscene eMails if one generates a separate query for each attribute access).

You can provide some degree of optimization in the Account object through accessing all the data at once on the first request. But that just adds more infrastructure complexity to the account management subsystem that it doesn't care about at all. Worse, I don't see any way to address optimization issues that span multiple objects. But that is easily managed in a subsystem dedicated to the paradigm and doing so will be quite transparent to the account management logic.

Perhaps the biggest problem of all, though, is reuse. Both the RDB and OODB paradigms for persistence are well and narrowly defined. They involve invariants that are readily abstracted (e.g., Schema/Table/Tuple). Once one isolates the persistence mechanisms one can abstract them in a generic fashion where differences in schemas can be parameterized in configuration data. Now one can reuse the entire subsystem across applications by simply providing a new interface and different configuration data.

In contrast, the CoP Account object is quite myopic about a very specific context. Worse, other CoP constructs like a Customer object will have to reinvent the persistence access mechanisms in their implementations.

No, it is not so. The thing is that the underlying logic of
representation and access can (and should) be encoded in a base concept
and inherited by child concepts. So if we want to develop a generic
access mechanism like persistent storage then we should define a base
concept, let's call it Persistent. After that we can include other
concepts into it (inherit from it, in OOP terms) like concepts Account
or Customer and they will automatically become persistent (their
instances, of course):

concept Persistent // Describes logic of persistent storage
reference { ... }
object { ... }

concept Account in Persistent // Describes logic of account management
reference { ... }
object { ... }
concept Customer in Persistent // Describes logic of customer management
reference { ... }
object { ... }

If Persistent storage needs to be transactional then we can include it
into the corresponding concept, say called Transactional:

concept Persistent in Transactional // Transactional behaviour
reference { ... }
object { ... }

So what is important here:

- Child concepts like Account and Customer implement only their own
business logic without external things like openDatabase(),
startTransaction() and lockRecord() spread all over their code which
then need to be manually maintained.

- Base concepts play the role of containers, wrappers, interceptors,
scopes, borders, spaces, environments etc.

- Base concepts actively and automatically contribute to the behaviour
provided by child concepts

- If we switch to an OODB then we simply change our base concept:
concept Account in PersistentOODB
concept Customer in PersistentOODB
Notice that concepts Account and Customer need not to be changed (if
developed correctly).

- In OOP base classes are not able to play such a role and even if they
provide some useful functions, these functions have to be called
explicitly from children using super methods, say, super.lockRecord().

- In OOP base objects cannot be shared at run-time and hence they cannot
serve as containers.

Where does one not want to use CoP? Within subsystem scope where collaborations are necessarily carnally intimate. What does one use instead? The standard 3GL reference mechanisms, which are ideally suited to relationship navigation as objects collaborate on a peer-to-peer basis. IOW, within a subsystem one has business as usual without any need for CoP.

Where does one want to use CoP? When one needs to hide the details of access when other access mechanisms are needed than the normal 3GL references. When does that occur? Only when one needs to access objects outside the subsystem scope. However, OO encapsulation already demands that such external objects be hidden behind an interface. So one already has a standard decoupling mechanism for that situation.

More important, decoupling across subsystems is even stronger than CoP's decoupling. That's because the objects that implement a subsystem are /completely/ hidden (i.e., other subsystems cannot know that they even exist). IOW, all collaboration communication is through the subsystem interface using pure messages (e.g., events).

In CoP a subsystem is described by a base concept into which child
concepts are included. For example, concept Persistent from the above
example is treated as a subsystem and hence any concept that is included
into it will be forced to use its laws at the same time getting useful
services. So an application is a hierarchy of such subsystems. In
particular, concept Account can also act as a subsystem by allowing
internal sub-accounts:

concept SavingsAccount in Account
reference { ... }
object { ... }

Notice that instances of SavingsAccount will live in the context of some
instance of Account which in turn will live in the context of some
persistent storage which has its own parent subsystem and so on up to
the root of the system.

As to efficiency, then CoP provides the mechanism of reference length
control where we can choose the scope of each reference. For example, if
we are writing a procedure for processing sub-accounts then it is known
to work in the context of one concrete main account. Hence it is natural
to represent sub-account objects by only their own id without main
account id and ids of other parent objects. It is analogous to sending
mails within one city when only street name is enough. Such access in
the context of one sub-system is obviously faster.

How do you express such phenomena as subsystem, abstraction, memory
management in OOP? I think OOP does not provide anything special for
that. For example, hypothetically we might propose to use the following
language constructs:

Subsystems are clearly and unambiguously defined at the OOA/D level. They are typically implemented using two-way Facade patterns to encapsulation them. [There is a description of the standard subsystem Bridge Model in my blog in the category on Application Partitioning.]

Yes, we can use various patterns to implement different types of
functions. But OOP itself does not enforce any concrete logic or
responsibilities. In other words, if we have a class diagram then how
can we figure out what class in it is a sub-system using only OO
specific declarations like inheritance? Actually, we cannot. Moreover,
even if we mark up our patterns in the diagram, say, we know that some
piece of code is a Facade pattern or Proxy pattern, even in this case we
cannot say that they implement something concrete like sub-system. Just
because patterns can be used for many different things. This is why I
said that OOP itself does not support the notion of sub-system or a
similar system structuring mechanism. In contrast, concepts in CoP do
allow us to implement a system hierarchy with internal sub-systems
because concepts have appropriate roles and responsibilities. So we can
do as follows:

concept MySystem // It is a system as one whole
reference {
String appName; // Id of app instances
}
object {
int appParameter; // Params
}


concept VisualSubsystem in Application // It is part of our system
reference {
int mainFrameId; // This app has many windows
}
reference {
String title; // Params
}

concept ModelSubsystem in Application // It is another part
reference { .. }
object { .. }

concept SubModel in ModelSubsystem
reference { .. }
object { .. }

So program objects will live in a hierarchy in a virtual address space.
The root of the hierarchy is the application identified by its name. So
any object reference will belong to some application and we need to
provide this reference segment in the general case. However, normally
procedures are executed in some local context, say, within SubModel. In
this case we need only local segments of references which are much
faster when accessed.

Notice that if these were classes in OOP (I mean Application,
VisualizationSubsystem etc.) then we would not be able to say that they
describe the structure of our system because inheritance is not intended
to be used for that purpose. On the other hand, if we remove reference
classes from the above concepts then they will turn into normal classes
(however, such a class hierarchy will hardly make sense).

It seems to me that as soon as you are making choices and introducing stacks you are introducing overhead compared to direct peer-to-peer collaboration.

No doubt, that all other things equal, CoP will be slower that OOP
accessing objects. Just like virtual functions in OOP are slower than
normal functions because such calls are intrinsically indirect. However,
the overall performance of a concept-oriented program could be higher
and here is why. What we are doing in OOP when we need to access an
object? We are making many auxiliary actions so a typical access looks
as follows:

// Example OOP
loadAccount(accNumber);
Object o = resolveAccountNumber(accNumber);
lockAccount(o);
checkPermissions(o);

o.getBalance(); // line 1: Direct call using primitive reference

unlockAccount(o);
unloadAccount(accNumber);

Here is what we are doing in CoP:

// Example CoP
account.getBalance(); // line 2: Indirect call using custom reference

You compare efficiency of line 1 and line 2 and it is not fair,
obviously. Of course, direct call is much faster than indirect one. But
indirect access does all the work that needs to be done manually (and
for each access) in OOP. In other words, line 2 implements all the
lines from example OOP. Thus these two approaches should not differ
significantly on overall performance because they execute one and the
same operations. The difference is /how/ we describe what operations
need to be executed and what modules are responsible for what.

In CoP we define a concept:

concept Account
reference { // Reference class
String accountNumber; // Identity attribute
}
object { // Object class
double balance; // Entity attribute
}

Here Account objects will be always represented by Account references.
We separate attributes (and functions) which are responsible for
representation and those belonging to the object.

I still don't get the benefit. A client object that needs the balance value still needs to navigate relationships to the right value somehow. With CoP that client now needs a 3GL reference to the reference object that, in turn, is a reference to access the object.balance.

No. All references are passed-by-value. So we do not have reference
objects: there are only references and objects. References are not
objects because they do not need a representative. In contrast, objects
always need a representative. So client always manipulates references
(which know how to provide access to objects). However, in OOP we can
model only objects (all references are primitive) while in CoP we can
model both references and objects.


I just don't see the gain. The client is still going to need a 3GL reference to get to the right Account object. Why not have that reference point directly to 'object'? The 'balance' implementation can still be encapsulated behind a getter so one has exactly the same degree of decoupling without the extra indirection.

Theoretically we can store 3GL references but it is not convenient - it
is the same as we would store direct physical addresses in memory. For
example, we cannot store this reference, we cannot pass it into another
context, we are not sure that it is still valid because of memory
constraints, we are not sure if we have rights to use it and so on. And
finally, as I noticed already, why at all we have to use what is
available instead of what is really needed in the problem domain? What
is important in CoP is that references and their functions reflect the
requirements to the problem domain and hence we need adequate means to
support it in a PL.

We cannot encapsulate the logic of access behind setters/getters because
object methods can be executed only when the object is already directly
accessible (it is a final step in access sequence). IOW, first, we
resolve a reference and then we call its object methods and it is not
possible to reverse this sequence. It would be also a design flaw
because setters/getter have to deal with what this class is intended to
and not what references cannot or do not want to do.

But in the OO paradigm nothing should be in between because all collaborations are supposed to be peer-to-peer. In an OO context inserting such behavior in such a hidden manner would be a serious design flaw and the reviewers would eat one's heart for doing that.

So it one of the differences between OO and CO approaches. CoP in this
sense is closer to AOP.

OTOH, if CoP is not meant to be OO, that's fine; maybe CoP will take over the software space. But it isn't an OO paradigm if solutions are constructed as you propose.

In fact, the only thing I have claimed (and one of the design goals) is
that CoP is backward compatible with the OOP main principles, i.e.,
under certain simplifying conditions we will get precisely what is
expected by an OO programmer. In its full featured form, CoP is
definitely significantly different from OOP.

--
http://conceptoriented.com

.



Relevant Pages

  • Re: Concept-oriented programming
    ... concepts into it (inherit from it, in OOP terms) like concepts Account ... reference {... ... So very specific code has to be provided somewhere in the hierarchy that is tailored to do that within all those {... ...
    (comp.object)
  • Re: Concept-oriented programming
    ... reference {... ... concept Account in Persistent // Describes logic of account management ... concept Customer in Persistent // Describes logic of customer management ... But why you think that objects in OOP are an exception and they have to be represented only by such system level identifiers? ...
    (comp.object)
  • Re: Crazy multi-level parsing of data
    ... A GL Code is a General Ledger Code or account. ... Capital and Operational expenses. ... Each tab represents only 1 GL account number, ... Assume an absolute reference on each GL tab at $B$7 where the GL code is ...
    (microsoft.public.excel.worksheet.functions)
  • RE: Forward or reply using other account
    ... reference to the Microsoft Office Object Model in the References dialog in ... Try Picture Attachments Wizard for Outlook: ... >> You can't change the sending account using the Outlook Object Model, ...
    (microsoft.public.outlook.program_vba)
  • Re: Concept-oriented programming
    ... account numbers or person SSNs. ... have to use primitive references for representing objects if we have ... I don't see indirection for its own sake is not a valid justification. ... I don't see anyway to do that within a subsystem except as part of the subsystem implementation. ...
    (comp.object)