Lect.14 - Software Engineering Laboratory

advertisement
ECE 355: Software Engineering
CHAPTER 8
Instructor
Kostas Kontogiannis
1
Course outline
• Unit 1: Software Engineering Basics
• Unit 2: Process Models and Software Life Cycles
• Unit 3: Software Requirements
• Unit 4: Unified Modeling Language (UML)
• Unit 5: Design Basics and Software Architecture
• Unit 6: OO Analysis and Design
 Unit 7: Design Patterns
• Unit 8: Testing and Reliability
• Unit 9: Software Engineering Management and Economics
2
References
• The material for this lecture was obtained
from
– http://www.dofactory.com/Patterns/Patterns.aspx#list
– http://www.developer.com/design/article.php/1502691
3
Adapter Design Pattern
• The Adapter is intended to provide a way for a
client to use an object whose interface is different
from the one expected by the client, without
having to modify either
• This pattern is suitable for solving issues that
arise, for example, when:
– 1) you want to replace one class with another and the
interfaces do not match, and
– 2) you want to create a class that can interact with other
classes without knowing their interfaces at design time
5
Adapter Design Pattern - Operation
• First, you create a custom Target class that defines methods using an
interface as expected by the client
• Second, you create a custom Adapter that implements the interface
expected by the Adaptee. The Adapter class subclasses the Target, and
provides an alternate implementation of the client requests in terms
that the Adaptee expects. Adapter overrides the Target method and
provides the correct interface for Adaptee.
• This approach has the advantage that it does not lead to modifications
in the client. Adapter is a structural pattern and you can use it to react
to changes in class interfaces as your system evolves, or you can
proactively use the Adapter pattern to build systems that anticipate
changing structural details
6
Adapter Design Pattern
7
Adapter Design Pattern
• Participants: The classes and/or objects participating in
this pattern are:
• Target
– defines the domain-specific interface that Client uses.
• Adapter
– adapts the interface Adaptee to the Target interface.
• Adaptee
– defines an existing interface that needs adapting.
• Client
– collaborates with objects conforming to the Target interface.
8
Adapter Design Pattern
// "Target"
// "Adaptee"
class Target
{
public virtual void Request()
{
Console.WriteLine("Called Target Request()");
}
}
class Adaptee
{
public void SpecificRequest()
{
Console.WriteLine("Called SpecificRequest()");
}
}
class Adapter : Target
{
private Adaptee adaptee = new Adaptee();
public override void Request()
{
// Possibly do some other work
// and then call SpecificRequest
adaptee.SpecificRequest();
}
}
}
// Client
class MainApp
{
static void Main()
{
// Create adapter and place a request
Target target = new Adapter();
target.Request();
// Wait for user
Console.Read();
Output
Called SpecificRequest()
}
}
9
Bridge Design Pattern - Operation
•
Decouple an abstraction from its implementation so that the two can vary
independently
•
The Bridge pattern is useful when there is a hierarchy of abstractions and a
corresponding hierarchy of implementations. Rather than combining the
abstractions and implementations into many distinct classes, the Bridge pattern
implements the abstractions and implementations as independent classes that
can be combined dynamically. Related patterns are :
– Layered Architecture
The Bridge design pattern is a way of organizing the entities identified using the
Layered Architecture pattern into classes.
– Abstract Factory
The Abstract Factory pattern can be used by the Bridge pattern to decide which
implementation class to instantiate for an abstraction object
10
Bridge Design Pattern
• Abstraction
– defines the abstraction's interface.
– maintains a reference to an object of type Implementor.
• RefinedAbstraction
– extends the interface defined by Abstraction.
• Implementor
– defines the interface for implementation classes. This interface doesn't
have to correspond exactly to Abstraction's interface; in fact the two
interfaces can be quite different. Typically the Implementation interface
provides only primitive operations, and Abstraction defines higher-level
operations based on these primitives.
• ConcreteImplementor
– implements the Implementor interface and defines its concrete
implementation.
11
Bridge Design Pattern
12
Bridge Design Pattern
// "Abstraction"
class Abstraction
{
protected Implementor implementor;
// Property
public Implementor Implementor
{
set{ implementor = value; }
}
public virtual void Operation()
{
implementor.Operation();
}
// "Implementor"
abstract class Implementor
{
public abstract void Operation();
}
// "RefinedAbstraction"
class RefinedAbstraction : Abstraction
{
public override void Operation()
{
implementor.Operation();
}
}
}
13
Bridge Design Pattern
// "ConcreteImplementorA"
class ConcreteImplementorA : Implementor
{
public override void Operation()
{
Console.WriteLine("ConcreteImplementorA Operation");
}
}
// "ConcreteImplementorB"
class ConcreteImplementorB : Implementor
{
public override void Operation()
{
Console.WriteLine("ConcreteImplementorB Operation");
}
}
14
Bridge Design Pattern - Client
class MainApp
{
static void Main()
{
Abstraction ab = new RefinedAbstraction();
// Set implementation and call
ab.Implementor = new ConcreteImplementorA();
ab.Operation();
// Change implemention and call
ab.Implementor = new ConcreteImplementorB();
ab.Operation();
// Wait for user
Console.Read();
}
}
Output
ConcreteImplementorA Operation
ConcreteImplementorB Operation
15
State Design Pattern - Operation
• Allow an object to alter its behavior when its internal state
changes. The object will appear to change its class.
• The State pattern is useful when you want to have an
object represent the state of an application, and you want to
change the state by changing that object.
• The State pattern is intended to provide a mechanism to
allow an object to alter its behavior in response to internal
state changes. To the client, it appears as though the object
has changed its class.
• The benefit of the State pattern is that state-specific logic is
localized in classes that represent that state.
16
State Design Pattern
Participants: The classes and/or objects participating in this
pattern are:
• Context
– defines the interface of interest to clients
– maintains an instance of a ConcreteState subclass that defines the
current state.
• State
– defines an interface for encapsulating the behavior associated with
a particular state of the Context.
• Concrete State
– each subclass implements a behavior associated with a state of
Context
17
State Design Pattern
18
State Design Pattern
// "State"
abstract class State
{
public abstract void Handle(Context context);
}
class Context
{
private State state;
public Context(State Astate)
{
this.state = Astate;
}
// "ConcreteStateA"
class ConcreteStateA : State
{
public override void Handle(Context context)
{
context.State = new ConcreteStateB();
}
}
// Property
public State State
{
get{ return state; }
set
{
state = value;
Console.WriteLine("State: " + state.GetType().Name);
}
}
// "ConcreteStateB"
class ConcreteStateB : State
{
public override void Handle(Context context)
{
context.State = new ConcreteStateA();
}
}
public void Request()
{
state.Handle(this);
}
}
19
State Design Pattern - Client
static void Main()
{
// Setup context in a state
Context c = new Context(new ConcreteStateA());
// Issue requests, which toggles state
c.Request();
c.Request();
c.Request();
c.Request();
// Wait for user
Console.Read();
}
}
Output
State: ConcreteStateA
State: ConcreteStateB
State: ConcreteStateA
State: ConcreteStateB
State: ConcreteStateA
20
Factory Method - Operation
• When developing classes, you always provide constructors
for your clients' use. There are certain circumstances in
which you do not want your clients to know which class
out of several to instantiate.
• The Factory Method is intended to define an interface for
clients to use to create an object, but lets subclasses decide
which class to instantiate. Factory Methods let a class defer
instantiation to subclasses.
21
Factory Method
• The classes and/or objects participating in this pattern are:
• Product
– defines the interface of objects the factory method creates
• ConcreteProduct
– implements the Product interface
• Creator
– declares the factory method, which returns an object of type Product.
Creator may also define a default implementation of the factory method
that returns a default ConcreteProduct object.
– may call the factory method to create a Product object.
• ConcreteCreator
– overrides the factory method to return an instance of a ConcreteProduct.
22
Factory Method
23
Factory Method
abstract class Product
{
}
// "ConcreteCreator"
class ConcreteCreatorA : Creator
{
public override Product FactoryMethod()
{
return new ConcreteProductA();
}
}
// "ConcreteProductA"
class ConcreteProductA : Product
{
}
// "ConcreteProductB"
class ConcreteProductB : Product
{
}
// "ConcreteCreator"
class ConcreteCreatorB : Creator
{
public override Product FactoryMethod()
{
return new ConcreteProductB();
}
}
// "Creator"
abstract class Creator
{
public abstract Product FactoryMethod();
}
}
24
Factory Method - Client
class MainApp
{
static void Main()
{
// An array of creators
Creator[] creators = new Creator[2];
creators[0] = new ConcreteCreatorA();
creators[1] = new ConcreteCreatorB();
// Iterate over creators and create products
foreach(Creator creator in creators)
{
Product product = creator.FactoryMethod();
Console.WriteLine("Created {0}",
product.GetType().Name);
}
// Wait for user
Console.Read();
}
}
Output:
Created ConcreteProductA
Created ConcreteProductB
25
Command Design Pattern - Operation
• The Command pattern is intended to encapsulate a request as an
object. For example, consider a window application that needs to make
requests of objects responsible for the user interface.
• The client responds to input from the user by creating a command
object and the receiver. It then passes this object to an Invoker object,
which then takes the appropriate action. This allows the client to make
requests without having to know anything about what action will take
place.
• In addition, you can change that action without affecting the client.
Practical uses for Command are for creating queues and stacks for
supporting "undo" operations, configuration changes, and so on.
26
Command Design Pattern
Participants: The classes and/or objects participating in this pattern are:
•
Command (e.g. Command)
– declares an interface for executing an operation
•
ConcreteCommand (e.g. CalculatorCommand)
– defines a binding between a Receiver object and an action
– implements Execute by invoking the corresponding operation(s) on Receiver
•
Client (e.g. CommandApp)
– creates a ConcreteCommand object and sets its receiver
•
Invoker (e.g. User)
– asks the command to carry out the request
•
Receiver (e.g. Calculator)
– knows how to perform the operations associated with carrying out the request
27
Command Design Pattern
28
Command Design Pattern
abstract class Command
{
protected Receiver receiver;
// Constructor
public Command(Receiver receiver)
{
this.receiver = receiver;
}
public abstract void Execute();
// "Receiver"
class Receiver
{
public void Action()
{
Console.WriteLine("Called Receiver.Action()");
}
}
}
// "ConcreteCommand"
class ConcreteCommand : Command
{
// Constructor
public ConcreteCommand(Receiver receiver) :
base(receiver)
{
}
// "Invoker"
class Invoker
{
private Command command;
public void SetCommand(Command command)
{
this.command = command;
}
public override void Execute()
{
receiver.Action();
}
public void ExecuteCommand()
{
command.Execute();
}
}
}
29
Command Design Pattern - Client
class MainApp
{
static void Main()
{
// Create receiver, command, and invoker
Receiver receiver = new Receiver();
Command command = new ConcreteCommand(receiver);
Invoker invoker = new Invoker();
// Set and execute command
invoker.SetCommand(command);
invoker.ExecuteCommand();
// Wait for user
Output
Called Receiver.Action()
Console.Read();
}
}
30
Proxy Design Pattern - Operation
• Provide a surrogate or placeholder for another object to
control access to it.
• This pattern is useful for situations where object creation is
a time consuming process and can make an application
appear sluggish. A proxy object can provide the client with
feedback while the object is being created.
• Proxy can also be used to provide a more sophisticated
reference to an object. For example, the proxy object can
implement additional logic on the objects behalf (security,
remote procedure calls, an so forth).
31
Proxy Design Pattern
Participants: The classes and/or objects participating in this pattern are:
•
Proxy (MathProxy)
– maintains a reference that lets the proxy access the real subject. Proxy may refer to a
Subject if the RealSubject and Subject interfaces are the same.
– provides an interface identical to Subject's so that a proxy can be substituted for for the
real subject.
– controls access to the real subject and may be responsible for creating and deleting it.
– other responsibilites depend on the kind of proxy:
• remote proxies are responsible for encoding a request and its arguments and for sending the
encoded request to the real subject in a different address space.
• virtual proxies may cache additional information about the real subject so that they can
postpone accessing it. For example, the ImageProxy from the Motivation caches the real
images's extent.
• protection proxies check that the caller has the access permissions required to perform a
request.
•
Subject (IMath)
– defines the common interface for RealSubject and Proxy so that a Proxy can be used
anywhere a RealSubject is expected.
•
RealSubject (Math)
– defines the real object that the proxy represents
32
Proxy Design Pattern
33
Proxy Design Pattern
// "Subject"
abstract class Subject
{
public abstract void Request();
}
// "Proxy"
class Proxy : Subject
{
RealSubject realSubject;
// "RealSubject"
public override void Request()
{
// Use 'lazy initialization'
if (realSubject == null)
{
realSubject = new RealSubject();
}
class RealSubject : Subject
{
public override void Request()
{
Console.WriteLine("Called
RealSubject.Request()");
}
}
realSubject.Request();
}
}
34
Proxy Design Pattern - Client
class MainApp
{
static void Main()
{
// Create proxy and request a service
Proxy proxy = new Proxy();
proxy.Request();
// Wait for user
Console.Read();
}
Output
Called RealSubject.Request()
}
35
Chain of Responsibility Design Pattern
• One of the tenets of software engineering is to keep objects
loosely coupled. The Chain of Responsibility is intended to
promote loose coupling between the sender of a request
and its receiver by giving more than one object an
opportunity to handle the request.
• The receiving objects are chained and pass the request
along the chain until one of the objects handles it.
• The result is to avoid coupling the sender of a request to its
receiver by giving more than one object a chance to handle
the request. Chain the receiving objects and pass the
request along the chain until an object handles it.
36
Chain of Responsibility Design Pattern
• Participants: The classes and/or objects participating in this pattern
are:
• Handler
– defines an interface for handling the requests
– (optional) implements the successor link
• ConcreteHandler
– handles requests it is responsible for
– can access its successor
– if the ConcreteHandler can handle the request, it does so; otherwise it
forwards the request to its successor
• Client
– initiates the request to a ConcreteHandler object on the chain
37
Chain of Responsibility Design Pattern
38
Chain of Responsibility Design Pattern
// "Handler"
abstract class Handler
{
protected Handler successor;
public void SetSuccessor(Handler successor)
{
this.successor = successor;
}
public abstract void HandleRequest(int request);
}
// "ConcreteHandler1"
class ConcreteHandler1 : Handler
{
public override void HandleRequest(int request)
{
if (request >= 0 && request < 10)
{
Console.WriteLine("{0} handled request {1}",
this.GetType().Name, request);
}
else if (successor != null)
{
successor.HandleRequest(request);
}
}
}
39
Chain of Responsibility Design Pattern
// "ConcreteHandler2"
class ConcreteHandler2 : Handler
{
public override void HandleRequest(int request)
{
if (request >= 10 && request < 20)
{
Console.WriteLine("{0} handled request {1}",
this.GetType().Name, request);
}
else if (successor != null)
{
successor.HandleRequest(request);
}
}
}
// "ConcreteHandler3"
class ConcreteHandler3 : Handler
{
public override void HandleRequest(int request)
{
if (request >= 20 && request < 30)
{
Console.WriteLine("{0} handled request {1}",
this.GetType().Name, request);
}
else if (successor != null)
{
successor.HandleRequest(request);
}
}
}
40
Chain of Responsibility Design Pattern
class MainApp
{
static void Main()
{
// Setup Chain of Responsibility
Handler h1 = new ConcreteHandler1();
Handler h2 = new ConcreteHandler2();
Handler h3 = new ConcreteHandler3();
h1.SetSuccessor(h2);
h2.SetSuccessor(h3);
// Generate and process request
int[] requests = {2, 5, 14, 22, 18, 3, 27, 20};
foreach (int request in requests)
{
h1.HandleRequest(request);
}
// Wait for user
Console.Read();
}
}
Output
ConcreteHandler1 handled request 2
ConcreteHandler1 handled request 5
ConcreteHandler2 handled request 14
ConcreteHandler3 handled request 22
ConcreteHandler2 handled request 18
ConcreteHandler1 handled request 3
ConcreteHandler3 handled request 27
ConcreteHandler3 handled request 20
41
Download