Decoupling Component Dependencies - PowerPoint PPT Presentation

1 / 44
About This Presentation
Title:

Decoupling Component Dependencies

Description:

To do this, we create a template for 'Item', which we won't instantiate until we ... You never create objects from them. Utility classes are used to: ... – PowerPoint PPT presentation

Number of Views:75
Avg rating:3.0/5.0
Slides: 45
Provided by: awo4
Category:

less

Transcript and Presenter's Notes

Title: Decoupling Component Dependencies


1
Decoupling Component Dependencies
  • Annatala Wolf
  • 222 Lecture 2

2
What is a dependency?
  • Dependencies are relationships in code where a
    change to one part of code necessitates a change
    to another part.
  • Example you write code to add functionality to
    Set_Kernel_1, then someone changes the code
    inside Set_Kernel_1. If your code depended on
    how Set_Kernel_1 was implemented, now it might be
    broken

3
Coupling
  • When components work together they are said to be
    coupled.
  • Loose coupling less dependency (good)
  • Tight coupling more dependency (bad)
  • Flexible code will feature components that are
    loosely coupled, so changes will not require a
    rewrite of the entire program.

4
Types of dependencies
  • Abstract-to-abstract loose coupling
  • example specification for Fancy_Set which
    extends (adds functionality to) specification for
    Set_Kernel
  • Concrete-to-abstract loose coupling
  • example a concrete implementation which
    implements Set_Kernel
  • Concrete-to-concrete tight coupling
  • example any time code depends on how another
    concrete component is implemented

5
Three situations where we might want to
decouple component dependencies
  • Checking components check the requires clauses of
    other components. They shouldnt depend on how
    the component is implemented.
  • Extensions add new behavior to a component.
    Often, they can be written without depending on
    any concrete code that implements the base
    component.
  • Utility classes contain static operations that we
    might need to use inside a component. We want to
    write components that use utility classes in a
    way that allows the client to select the
    functionality.

6
Decoupling in Resolve/C
  • To solve most of these problems in C,
    programmers would typically use polymorphism.
  • In Resolve/C, we instead use C templates to
    accomplish this. The benefits of this method are
    that it produces code which is easy to read, and
    it avoids the use of multiple-inheritance, which
    is error-prone.
  • The downside is the cryptic error messages that
    result from using templates in complex ways.

7
Checking components for debugging
  • Checking components are components that check the
    requires clauses of other components.
  • Youve been using checking components since 221
    (the ones that end in _C are the checking
    versions).
  • These components give you violated assertion
    messages when you break a precondition.
  • Theyre useful when you need to check code for
    correctness, but they can be replaced with the
    non-checking versions when the code works (for
    speed). This is a common paradigm in program
    design.

8
Component Coupling Diagrams
  • The diagrams we use in 222 are called component
    coupling diagrams or CCD for short. CCD are
    similar to diagrams used in industry, like UML
    class diagrams.
  • CCD illustrate the way components depend on each
    other. Each arrow represents a kind of
    dependency. The component the arrow points from
    depends on the one it points to.

9
The checks dependency
  • The checks dependency occurs when one component
    checks the requires clauses of another
    components operations.

This CCD illustrates a concrete-to-concrete
dependency, which wed like to avoid. We dont
want to write a separate checker for each
implementation of Sequence.
10
How checking components work
  • For every operation with a requires clause, a
    checking component overrides the operation with
    its own version.
  • First, it runs assertions that check the
    preconditions.
  • If the assertions pass, the override operation
    calls the original (unchecked) operation, so it
    will do what the client expects it to do.
  • If an operation has no requires clause, it does
    not appear in the checking component, so the
    original operation is called automatically.

11
Operations of Queue_Kernel_C
  • procedure_body Dequeue (produces Item x)
  • assert (self.Length () gt 0, "self /
    empty_string")
  • self.Queue_BaseDequeue (x)
  • function_body Item operator
  • (preserves Accessor_Position current)
  • assert (self.Length () gt 0, "self /
    empty_string")
  • return self.Queue_Baseoperator
    (current)
  • // We dont need any code for Enqueue() or
    Size(),
  • // because their requires clause is always true.

12
Generifying the process of checking
  • Notice how the requires clause is specified in
    the abstract component, not in the
    implementation.
  • Since all implementations use the same abstract
    component, we should only need to write a single
    checking component for Set_Kernel, not one
    component for each version of Set_Kernel. The
    checker shouldnt need to know which
    implementation of Set its checking.

13
Decoupling checks
  • Instead of checking a particular component, well
    check a component whose type gets passed to the
    checker as a template parameter (the black box).
  • This parameter is named Sequence_Base. It can
    be filled in with any implementation of Sequence.
    The implementation to use is chosen by the
    client.

14
Clients choice
  • In order to instantiate Sequence_C, the client
    must plug two types in to fill out its template
  • Item (what type the Sequence will hold)
  • Sequence_Base (which implementation of
    Sequence_Kernel the client wants to use)
  • Once both of these parameters are filled in with
    actual types, the client can make
    automatically-checked Sequence objects.

15
Partial instantiation
  • The reason you dont see this is that we
    partially instantiate most of the templates
    youve been using.
  • This means we fill in part of the template for a
    component like Queue_Kernel_C, for example, by
    filling in Queue_Base with Queue_Kernel_2.
    However, we leave other parts, such as Item, as
    template parameters that still need to be filled
    in.
  • We call this new concrete template (still
    templated by Item) Queue_Kernel_2_C. You only
    need to fill in a type for Item to instantiate
    Queue_Kernel_2_C.

16
Partial instantiation example( Item is kept as a
template parameter )
  • concrete_template lt
  • concrete_instance class Item
  • gt
  • class Sequence_Kernel_1a_C
  • specializes
  • concrete_instance Sequence_Kernel_C lt
  • Item,
  • Sequence_Kernel_1a ltItemgt
  • gt

17
Partial instantiation CCD
  • Heres how the creation of Sequence_Kernel_1a_C
    (through partial instantiation) is related to
    other components.

18
The specializes and uses arrows
  • The specializes dependency just means this
    depends on that, because it fills in some of that
    components template parameters. It is only
    used to denote partial instantiation.
  • The uses dependency is a generic dependency
    that means, this needs that component for some
    reason. In this case, we need
    Sequence_Kernel_1a, because its the type were
    plugging into Sequence_Base.

19
Extensions adding functionality
  • What if you want to add new behavior to a
    component? Maybe you want to create a Fancy_Set
    component that includes operations for Union,
    Intersection, Set_Difference, etc.
  • Ideally, wed like to design Fancy_Set to work
    with any implementation of Set_Kernel. If it
    depended on a particular implementation, wed
    only be able to use it with that one version of
    Set_Kernel, and it might break if code changes.

20
Extending the specification
  • The behavior of a component is described by the
    abstract component (here, Set_Kernel). This is
    what specifications dothey describe the model,
    and a contract for its behavior.
  • The behavior of an extended component will also
    be described by an abstract model (here,
    Fancy_Set_Kernel). The relationship here is an
    abstract-to-abstract dependency (loose coupling,
    which is good).

21
The extends dependency
  • The extends arrow means this adds
    functionality to the component described in
    that. (Note that extends is the only dependency
    you will see between two abstract components.)
  • You can use extends between concrete components
    too, but this illustrates a concrete-to-concrete
    dependency. Wed like to avoid this if possible.

22
Layered extensions
  • Ideally, wed like to design an implementation of
    Sequence_Reverse that will work with any version
    (any particular implementation) of
    Sequence_Kernel.
  • If we build Sequence_Reverse as a client of
    Sequence (by simply calling the operations of
    Sequence_Kernel), then we dont need to know how
    its operations are implemented.
  • A component that works this way is called a
    layered extension. Its a client of the component
    beneath it, so its code will work with any
    implementation.

23
Decoupling extends
  • To decouple the dependency, we can use templates
    in a similar way to how we decoupled checking.

C-to-C Dependency
Decoupled!
24
What to take from this
  • Make sure you understand the component decoupling
    example on the previous slide.
  • You should understand why we want to decouple
    concrete-to-concrete dependencies in
    component-based software development, and how we
    accomplish it through templates.
  • Although the template paradigm is not the typical
    way to do this in C, the need to limit
    concrete-concrete dependencies is a very common
    issue faced in systems programming.

25
Note Item does not appear in CCD
  • The black box is always a template parameter for
    which implementation of an abstract class the
    client wants to use.
  • It is not Item! Item is not shown as a
    component in any CCD, except that templates
    always have a bold outline, so Item is in some
    cases the cause.
  • Here, the black box is a parameter for a
    particular Sequence_Kernel implementation.
  • The name of the parameter is Sequence_Base.

26
How to write a layered extension
  • Inside a layered extension, the keyword self
    refers to the base component (here, a Sequence of
    Item), and you call operations on self to change
    the Sequence that gets passed as the
    distinguished parameter.
  • procedure_body Reverse ()
  • object Integer frontIndex
  • object Integer backIndex self.Length()-1
  • while (frontIndex lt backIndex)
  • selffrontIndex selfbackIndex
  • frontIndex
  • backIndex--

27
How self is passed
  • // My_Sequence is a Sequence_Reverse_1 type that
    is
  • // instantiated by ltText, Sequence_Kernel_1altTextgt
    gt
  • object My_Sequence names
  • names.Add(name1)
  • names.Add(name2)
  • names.Reverse()
  • Since Sequence_Reverse_1 is written as a layered
    extension inside Reverse(), self (in this case)
    is names passed by reference.

28
Type of self in layered extensions
  • In a layered extension, the type of self is the
    same type as the component youre writing. You
    can use it to make objects of the same type as
    self, if you need to.
  • procedure_body Intersect( preserves Set_Intersect
    ltItemgt s)
  • object catalyst Set_Intersect_1 temp
  • while (self.Size () gt 0)
  • object Item x
  • self.Remove_Any (x)
  • if (s.Is_Member (x))
  • temp.Add (x)
  • self temp

Set_Intersect is templated by Item. Inside
Intersect(), we can use Item just like any type.
The type of self here is Set_Intersect_1.
29
Using Item
  • Inside template-parameterized code, we can use a
    parameter like Item just like any concrete type,
    even though we dont know what it is. It will
    have all four default operations, since clients
    are required to use Resolve objects in
    instantiations.
  • Some contracts may add a restriction about what
    you can plug in for Item. Set_Kernel, for
    example, requires any Item chosen for Set must
    contain the Is_Equal_To() function.
  • So if youre writing something for Set, you can
    assume Is_Equal_To() exists for Item, if needed.

30
Default Resolve/C operations
  • There are four default Resolve/C operations.
    These exist for all Resolve/C objects (except
    Pointer and Pointer_C). They even exist for
    unknown, non-pointer objects, like Item.
  • (constructor) all objects created set to a
    default value
  • (destructor) objects handle cleanup implicitly
  • a b swaps objects a and b
  • a.Clear() resets a to the default value for its
    type
  • The existence of allows us to move Item
    objects around without copying or using pointers.

31
Non-layered extensions
  • Sometimes, simply layering an extension on the
    base component would give us bad performance. In
    this case, we need to do extra work to get the
    performance we need.
  • Writing a non-layered extension is just like
    writing a Kernel, except that youre writing all
    the Kernel operations, plus the new operations
    from the extension. Youll do this for Lab 5.

32
Kernels and non-layered extensions
  • In Kernels and non-layered extensions, the
    keyword self stands for something called the
    Representation. This is a different use from
    layered extensions, so dont get confused. Well
    cover the different use of self in Kernels later.
  • (We also use the term layered with Kernels,
    but it means something different. A Kernel is
    layered if its built using other components,
    such as using a Sequence to implement Queue. A
    Kernel is non-layered if it uses pointers.)

33
Utility classes code, but no data
  • Utility classes are different from other
    components, because they do not define a type.
    You never create objects from them.
  • Utility classes can be used to
  • Package a bunch of static operations together
  • Package one or more operations in a class, so we
    can plug the class into a template and let the
    client select which version of an operation they
    want to use as part of instantiation

34
Utility class syntax
  • Since you cant make objects of a utility class,
    its operations are static, and are called by
    using the class name as a prefix. Examples
  • // get the hash value for Text object name
  • hash Text_HashHash(name)
  • // compare two Foo objects, see which comes first
  • flag Foo_Are_In_Order_2Are_In_Order(a, b)

35
Is_Equal_To( ) syntax
  • Is_Equal_To() uses a different syntax, partly
    because there should only be one version of
    equality for a given type, and partly because
    its more efficient to implement it inside a
    Kernel.
  • Because of this, when a spec tells you that you
    have access to Is_Equal_To() for a type (like
    Item), you call it as an instance operation.
  • // function returns true iff item1 item2
  • answer item1.Is_Equal_To(item2)

36
Equality with default types
  • Note that the five basic default types in Resolve
    (Boolean, Character, Integer, Real, and Text) all
    have Is_Equal_To() implemented. In each case,
    its identical to the operator for that
    type.
  • Why do these types need Is_Equal_To() if they
    have ? Well, say we instantiate Set_Kernel_1
    with Text. The code in Set_Kernel_1 is designed
    to work for any Item type that has
    Is_Equal_To() written for it. So Is_Equal_To()
    is the operation Set_Kernel_1 will call to test
    equality for Item.

37
Using utility classes Queue_Sort
  • Queue_Sort is an extension of Queue. It needs to
    know how to sort data, but Queue could be
    instantiated with many different types of Item.
    We could also want to sort the same type of Item
    in different ways (ascending, descending, etc).
  • We can prevent the need to write a different
    Queue_Sort for each type of data (and for each
    way to order a type) by plugging in an
    implementation of the utility class Are_In_Order
    into an implementation of Queue_Sort.

38
Utility Class CCD
  • Note that Queue_Sort_1 is a template with three
    parameters Queue_Base, Item, and a version of
    Are_In_Order for Item.

39
The meaning of parameters, and the version of
Queue_Sort selected
  • Queue_Base tells us which concrete implementation
    of Queue_Kernel we will use as the engine for
    our new Queue_Sort type.
  • Item tells us what type of data our new
    Queue_Sort type will hold.
  • Are_In_Order has to match the type for Item it
    says what order the Items will be sorted in.
  • The version of Queue_Sort we choose to
    instantiate will determine how the data is
    sorted, in code (which sorting algorithm is used).

40
Know your dependencies
  • There are six dependencies (A ? B)
  • checks A checks Bs requires clauses
  • implements As code implements Bs specs
  • extends A adds new functionality to B
  • specializes A partially instantiates B
  • uses A needs B for something (generic)
  • encapsulates A contains Representation B(used
    only with Kernels described later)

41
Local operations(Implementer Perspective)
  • We sometimes use private operations to assist us
    in writing components. In Resolve, we call these
    local operations.
  • Private means a local operation can only be
    called from within the class were implementing.
    It is written just like a global operation (from
    a static context), but you can only call it from
    within the class.

42
Local operation context
  • It is easier to reason about the behavior of
    local operations if they never access self,
    particularly inside Kernels (but also in layered
    extensions).
  • This way, you dont have to worry about a local
    operation messing up the object the class
    represents. You can even write code for a local
    operation without knowing what class youre in.
  • We write local operations statically everything
    it needs will be passed as an operation
    parameter. If it needs access to self, you can
    just pass self (or in Kernels, a field of self)
    as an argument.

43
Self-discipline
  • You can use self in local operations, but dont!
    The way weve written them, you dont have to.
    Just implement the specification, and pass self
    to the operation if you need to.
  • One reason we have local operations is so you can
    perform recursive steps there, since recursion is
    not allowed in Kernel operations (its fine in
    layered extensions). Well explain why later,
    but this is another requirement where you can do
    something, but shouldnt.

44
Kinds of operations(implementers perspective)
Write a Comment
User Comments (0)
About PowerShow.com