Download presentation
Presentation is loading. Please wait.
Published byColin James Modified over 9 years ago
1
1 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Franco Gasperoni gasperoni@adacore.com http://libre.adacore.com
2
2 © AdaCore under the GNU Free Documentation License Copyright Notice © ACT Europe under the GNU Free Documentation License Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation; provided its original author is mentioned and the link to http://libre.act-europe.fr/ is kept at the bottom of every non-title slide. A copy of the license is available at: http://www.fsf.org/licenses/fdl.html
3
3 http://libre.adacore.com © AdaCore under the GNU Free Documentation License
4
4 http://libre.adacore.com © AdaCore under the GNU Free Documentation License What is the largest software system you have built ? (in SLOC)
5
5 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Small Software Systems Understandable by 1 person Can throw away & replace it to –repair / extend –port to new platform Anything is OK for small systems (< 10 Ksloc)
6
6 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Medium/Large Systems Team of people No 1 person knows all its aspects Long life-span (> 10 years) CANNOT throw away & replace it to –repair / extend –port to new platform Requires organization, discipline & right tools
7
7 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Programming in the Large –specification & implementation –privacy –abstract data types –hierarchical packages
8
8 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Separate Compilation THE PROBLEMTHE PROBLEM Compiler object CODECODE Compiler object CODECODE Compiler object CODECODE LinkerLinker executable libraries
9
9 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Problem with this approach CODECODE CODECODE CODECODE No structure To write your own code –YOU MUST understand everybody else’s code CODECODE CODECODE
10
10 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Idea SPECIFY WHAT EACH MODULE SHOULD DO
11
11 http://libre.adacore.com © AdaCore under the GNU Free Documentation License SPECIFICATION Software module ? BODY
12
12 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Programming in the Large –specification & implementation specification implementation specification rules in Ada
13
13 http://libre.adacore.com © AdaCore under the GNU Free Documentation License A Specification is a... CONTRACT Implementor of the module Users/clients of the module On the SERVICES provided by the module
14
14 http://libre.adacore.com © AdaCore under the GNU Free Documentation License SPEC = list of services provided BODY = implementation of the services (hidden) Software module Service_1 Service_2 Service_3 Service_1 implementation Service_2 implementation Service_3 implementation
15
15 http://libre.adacore.com © AdaCore under the GNU Free Documentation License SPECIFICATION ? BODY
16
16 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Example Create a Queue module that can –Add an Integer to the Queue –See the First integer in the Queue –Get the first integer in the Queue –Test whether the Queue is Empty
17
17 http://libre.adacore.com © AdaCore under the GNU Free Documentation License package Queue is procedure Add (Element : Integer); function First return Integer; function Get return Integer; function Empty return Boolean; end Queue; package Queue is procedure Add (Element : Integer); function First return Integer; function Get return Integer; function Empty return Boolean; end Queue; queue.ads
18
18 http://libre.adacore.com © AdaCore under the GNU Free Documentation License package Queue ? BODY package Queue is procedure Add (Element : Integer); function First return Integer; function Get return Integer; function Empty return Boolean; end Queue; package Queue is procedure Add (Element : Integer); function First return Integer; function Get return Integer; function Empty return Boolean; end Queue; queue.ads
19
19 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Using package Queue with Queue; procedure Client is Queue_Error : exception; X : Integer; begin Queue.Add (3); Queue.Add (4); if not Queue.Empty then X := Queue.Get; else raise Queue_Error; end if; end Client; client.adb
20
20 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Specifications Reduce Complexity SPEC To write your own code –only need to understand specs for the services you need
21
21 http://libre.adacore.com © AdaCore under the GNU Free Documentation License package Queue ? package Queue is procedure Add (Element : Integer); function First return Integer; function Get return Integer; function Empty return Boolean; end Queue; package Queue is procedure Add (Element : Integer); function First return Integer; function Get return Integer; function Empty return Boolean; end Queue; queue.ads To write Client only need to look at
22
22 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Aside: use clause with Queue; use Queue; procedure Client is Queue_Error : exception; X : Integer; begin Queue. Add (3); Queue. Add (4); if not Queue. Empty then X := Queue. Get; else raise Queue_Error; end if; end Client;
23
23 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Programming in the Large –specification & implementation specification implementation specification rules in Ada
24
24 http://libre.adacore.com © AdaCore under the GNU Free Documentation License ONE possible implementation of package Queue This implementation raises Constraint_Error if more than Max_Size elements are put in the Queue.
25
25 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Circular Buffer Q 01 Max_Size - 1 Q_Last Q_First
26
26 http://libre.adacore.com © AdaCore under the GNU Free Documentation License package body Queue is Max_Size : constant := 100; type Q_Index is mod Max_Size; Q : array (Q_Index range 0.. Max_Size - 1) of Integer; Q_First : Q_Index := Q ’ First; Q_Last : Q_Index := Q_First; Size : Natural range 0.. Max_Size; procedure Add (Element : Integer) is begin Q (Q_Last) := Element; Q_Last := Q_Last + 1; Size := Size + 1; end Add;... end Queue; queue.adb
27
27 http://libre.adacore.com © AdaCore under the GNU Free Documentation License package body Queue is... function First return Integer is begin return Q (Q_First); end First; function Get return Integer is begin Q_First := Q_First + 1; Size := Size - 1; return Q (Q_First - 1); end Get; function Empty return Boolean is begin return Size = 0; end Empty; end Queue; queue.adb
28
28 http://libre.adacore.com © AdaCore under the GNU Free Documentation License ANOTHER possible implementation of package Queue
29
29 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Linked List Q_Last Q_First Free
30
30 http://libre.adacore.com © AdaCore under the GNU Free Documentation License package body Queue is type Queue_Element; type Element_Ptr is access Queue_Element; type Queue_Element is record Val : Integer; Next : Element_Ptr; end record; Q_First : Element_Ptr; Q_Last : Element_Ptr; Free : Element_Ptr := new Queue_Element;... end Queue; queue.adb
31
31 http://libre.adacore.com © AdaCore under the GNU Free Documentation License package body Queue is... procedure Add (Element : Integer) is begin if Q_First = null then Q_First := Free; else Q_Last.Next := Free; end if; Q_Last := Free; Free := Free.Next; Q_Last.all := (Element, null); if Free = null then Free := new Queue_Element; end if; end Add;... end Queue; queue.adb
32
32 http://libre.adacore.com © AdaCore under the GNU Free Documentation License package body Queue is... function Get return Integer is Tmp : Element_Ptr := Q_First; begin Q_First := Q_First.Next; if Q_First = null then Q_Last := null; end if; Tmp.Next := Free; Free := Tmp; return Tmp.Val; end Get;... end Queue; queue.adb
33
33 http://libre.adacore.com © AdaCore under the GNU Free Documentation License package body Queue is... function First return Integer is begin return Q_First; end First; function Empty return Boolean is begin return Q_First = null; end Empty; end Queue; queue.adb
34
34 http://libre.adacore.com © AdaCore under the GNU Free Documentation License A Spec can have several implementations Can change implementation WITHOUT having to change ANY of the client’s code package Queue is procedure Add (Element : Integer); function First return Integer; function Get return Integer; function Empty return Boolean; end Queue; package Queue is procedure Add (Element : Integer); function First return Integer; function Get return Integer; function Empty return Boolean; end Queue; queue.ads first implement. first implement. second implement. second implement.
35
35 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Programming in the Large –specification & implementation specification implementation specification rules in Ada
36
36 http://libre.adacore.com © AdaCore under the GNU Free Documentation License In Ada Spec always checked against implementation Must with the specs that you are going to use (not in C) Packages provide multiple name spaces
37
37 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Spec is checked against its body package Queue is procedure Add (Element : Integer);... end Queue; package Queue is procedure Add (Element : Integer);... end Queue; package body Queue is... procedure Add (Element : Integer; X : Float) is... end Add;... end Queue; package body Queue is... procedure Add (Element : Integer; X : Float) is... end Add;... end Queue; Compilation error
38
38 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Must with Specs used with Queue; procedure Client is... begin Queue.Add (3);... end Client; Compilation error
39
39 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Multiple Name Spaces package Queue is procedure Add (E : Integer);... end Queue; package Queue is procedure Add (E : Integer);... end Queue; package Set is procedure Add (E : Integer);... end Set; package Set is procedure Add (E : Integer);... end Set; with Queue; with Set; procedure Client is begin Queue.Add (3); Set.Add (99); end Client;
40
40 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Use Clause and Ambiguities package Queue is procedure Add (E : Integer);... end Queue; package Queue is procedure Add (E : Integer);... end Queue; package Set is procedure Add (E : Integer);... end Set; package Set is procedure Add (E : Integer);... end Set; with Queue; use Queue; with Set; use Set; procedure Client is begin Add (123); end Client; Compilation error ambiguity
41
41 http://libre.adacore.com © AdaCore under the GNU Free Documentation License But … Ada has overloading package Queue is procedure Add (E : Integer); procedure Add (E : Float);... end Queue; package Queue is procedure Add (E : Integer); procedure Add (E : Float);... end Queue; with Queue; use Queue; procedure Client is begin Add (123); Add (3.141); end Client;
42
42 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Programming in the Large –specification & implementation –privacy –abstract data types –hierarchical packages
43
43 http://libre.adacore.com © AdaCore under the GNU Free Documentation License package Queues is type Queue is …; procedure Add (Q : Queue; Element : Integer); function First (Q : Queue) return Integer; function Get (Q : Queue) return Integer; function Empty (Q : Queue) return Boolean; end Queues; package Queues is type Queue is …; procedure Add (Q : Queue; Element : Integer); function First (Q : Queue) return Integer; function Get (Q : Queue) return Integer; function Empty (Q : Queue) return Boolean; end Queues; Having Several Queues
44
44 http://libre.adacore.com © AdaCore under the GNU Free Documentation License package Queues is type Queue is …; end Queues; package Queues is type Queue is …; end Queues; !!! WARNING !!! Use Different names
45
45 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Using Several Queues with Queues; use Queues; procedure Client is Q1 : Queue; Q2 : Queue; begin Add (Q1, 123); Add (Q2, 3); Add (Q2, Get (Q1)); end Client;
46
46 http://libre.adacore.com © AdaCore under the GNU Free Documentation License One possible implementation... package Queues is type Queue is …; procedure Add (Q : Queue; Element : Integer); function First (Q : Queue) return Integer; function Get (Q : Queue) return Integer; function Empty (Q : Queue) return Boolean; end Queues; type Q_Element; type Element_Ptr is access Queue_Element; type Queue_Element is record Val : Integer; Next : Element_Ptr; end record; type Queue is record First : Element_Ptr; Last : Element_Ptr; end record; type Q_Element; type Element_Ptr is access Queue_Element; type Queue_Element is record Val : Integer; Next : Element_Ptr; end record; type Queue is record First : Element_Ptr; Last : Element_Ptr; end record;
47
47 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Client code allowed to depend on the implementation ! with Queues; use Queues; procedure Client is Q1 : Queue; Q2 : Queue; begin Add (Q1, 123); Add (Q2, 3); Q2.Last := null; end Client; OK
48
48 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Another implementation... package Queues is type Queue is …; procedure Add (Q : Queue; Element : Integer); function First (Q : Queue) return Integer; function Get (Q : Queue) return Integer; function Empty (Q : Queue) return Boolean; end Queues; Max_Size : constant := 100; type Q_array (Natural range <>) of Integer; type Queue is record Q : Q_Array (0.. Max_Size); First : Natural; Last : Natural; Size : Natural; end record; Max_Size : constant := 100; type Q_array (Natural range <>) of Integer; type Queue is record Q : Q_Array (0.. Max_Size); First : Natural; Last : Natural; Size : Natural; end record;
49
49 http://libre.adacore.com © AdaCore under the GNU Free Documentation License … breaks client code ! with Queues; use Queues; procedure Client is Q1 : Queue; Q2 : Queue; begin Add (Q1, 123); Add (Q2, 3); Q2.Last := null; end Client; Compilation error
50
50 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Even without changing the implementation there is a PROBLEM with Queues; use Queues; procedure Client is Q1 : Queue; Q2 : Queue; begin Add (Q1, 123); Add (Q2, 3); Q2.Last := null; end Client; Q2 is in an inconsistent state 3 3 First Last null Q2:
51
51 http://libre.adacore.com © AdaCore under the GNU Free Documentation License You need PRIVACY Exposing your data structures is risky –Client code may manipulate the structures directly without using your own services –Client code is hard to change
52
52 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Think BIG what if the Queues package is used by 1000s of other packages
53
53 http://libre.adacore.com © AdaCore under the GNU Free Documentation License If there is a bug concerning a Queue, you may have to look at 1000s of packages to find the bug If you change the implementation you may have to update 1000s of packages
54
54 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Programming in the Large –privacy private types private types & discriminants limited private types
55
55 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Private types package Queues is type Queue is private; procedure Add (Q : Queue; Element : Integer); function First (Q : Queue) return Integer; function Get (Q : Queue) return Integer; function Empty (Q : Queue) return Boolean; private type Queue is …; end Queues; package Queues is type Queue is private; procedure Add (Q : Queue; Element : Integer); function First (Q : Queue) return Integer; function Get (Q : Queue) return Integer; function Empty (Q : Queue) return Boolean; private type Queue is …; end Queues;
56
56 http://libre.adacore.com © AdaCore under the GNU Free Documentation License In any implementation... package Queues is type Queue is private; procedure Add (Q : Queue; Element : Integer); function First (Q : Queue) return Integer; function Get (Q : Queue) return Integer; function Empty (Q : Queue) return Boolean; private type Queue_Element; type Element_Ptr is access Queue_Element; type Queue_Element is record Val : Integer; Next : Element_Ptr; end record; type Queue is record First : Element_Ptr; Last : Element_Ptr; end record; end Queues;
57
57 http://libre.adacore.com © AdaCore under the GNU Free Documentation License … private types are PRIVATE with Queues; use Queues; procedure Client is Q1 : Queue; Q2 : Queue; begin Add (Q1, 123); Add (Q2, 3); Q2.Last := null; end Client; Compilation error
58
58 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Advantages of private types Enforces the contract of a specification No client code can corrupt your data structures Can change implementation without changing client code
59
59 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Why is the private part in the spec ? package Queues is type Queue is private; procedure Add (Q : Queue; Element : Integer); function First (Q : Queue) return Integer; function Get (Q : Queue) return Integer; function Empty (Q : Queue) return Boolean; private type Queue_Element; type Element_Ptr is access Queue_Element; type Queue_Element is record Val : Integer; Next : Element_Ptr; end record; type Queue is record First : Element_Ptr; Last : Element_Ptr; end record; end Queues;
60
60 http://libre.adacore.com © AdaCore under the GNU Free Documentation License … because we still need to compile the clients code with Queues; use Queues; procedure Client is Q1 : Queue; begin Add (Q1, 123); end Client;
61
61 http://libre.adacore.com © AdaCore under the GNU Free Documentation License … but you can make a private type quite private package Queues is type Queue is private; procedure Add (Q : Queue; Element : Integer); function First (Q : Queue) return Integer; function Get (Q : Queue) return Integer; function Empty (Q : Queue) return Boolean; private type Queue_Info; type Queue is access Queue_Info; end Queues;
62
62 http://libre.adacore.com © AdaCore under the GNU Free Documentation License package body Queues is type Queue_Element; type Element_Ptr is access Queue_Element; type Queue_Element is record Val : Integer; Next : Element_Ptr; end record; type Queue_Info is record First : Element_Ptr; Last : Element_Ptr; end record;... end Queues;
63
63 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Programming in the Large –privacy private types private types & discriminants limited private types
64
64 http://libre.adacore.com © AdaCore under the GNU Free Documentation License package Queues is type Queue (Max_Size : Natural) is private; procedure Add (Q : Queue; Element : Integer); function First (Q : Queue) return Integer; function Get (Q : Queue) return Integer; function Empty (Q : Queue) return Boolean; private type Q_array (Natural range <>) of Integer; type Queue (Max_Size : Natural) is record Q : Q_Array (0.. Max_Size); First : Natural; Last : Natural; Size : Natural; end record; end Queues;
65
65 http://libre.adacore.com © AdaCore under the GNU Free Documentation License with Queues; use Queues; procedure Client is Q1 : Queue (100); Q2 : Queue (250); begin Add (Q1, 123); Add (Q2, 3); end Client; Q1 can have up to 100 elmts Q2 can have up to 250 elmts
66
66 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Programming in the Large –privacy private types private types & discriminants limited private types
67
67 http://libre.adacore.com © AdaCore under the GNU Free Documentation License with Queues; use Queues; procedure Client is Q1 : Queue; Q2 : Queue; X : Integer; begin Add (Q1, 123); Add (Q1, 3); Q2 := Q1; X := Get (Q2); end Client; Does this affect Q1 ?
68
68 http://libre.adacore.com © AdaCore under the GNU Free Documentation License it depends on … the implementation ! If a Queue is implemented with –a pointer then Get (Q2) MODIFIES Q1 –a record containing an array then Get (Q2) does NOT modify Q1
69
69 http://libre.adacore.com © AdaCore under the GNU Free Documentation License with Queues; use Queues; procedure Client is Q1 : Queue; Q2 : Queue; X : Integer; begin Add (Q1, 123); Add (Q1, 3); Q2 := Q1; X := Get (Q2); end Client; 123 Q1: 3 3 Q2: 123 Q1: 123 Q1: 3 3 Q2: Pointer implementation
70
70 http://libre.adacore.com © AdaCore under the GNU Free Documentation License limited private types NO assignment := NO equality comparison =
71
71 http://libre.adacore.com © AdaCore under the GNU Free Documentation License package Queues is type Queue is limited private; procedure Add (Q : Queue; Element : Integer); function First (Q : Queue) return Integer; function Get (Q : Queue) return Integer; function Empty (Q : Queue) return Boolean; private type Queue is …; end Queues; package Queues is type Queue is limited private; procedure Add (Q : Queue; Element : Integer); function First (Q : Queue) return Integer; function Get (Q : Queue) return Integer; function Empty (Q : Queue) return Boolean; private type Queue is …; end Queues;
72
72 http://libre.adacore.com © AdaCore under the GNU Free Documentation License with Queues; use Queues; procedure Client is Q1 : Queue; Q2 : Queue; X : Integer; begin Add (Q1, 123); Add (Q1, 3); Q2 := Q1; end Client; COMPILATION ERROR := forbidden
73
73 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Programming in the Large –specification & implementation –privacy –abstract data types –hierarchical packages
74
74 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Queue is an Abstract Data Type (ADT) Set of abstract values (data domain) collection of abstract Operations (routines that manipulate the values) ADT =
75
75 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Queue is an ADT package Queues is type Queue is limited private; procedure Add (Q : Queue; Element : Integer); function First (Q : Queue) return Integer; function Get (Q : Queue) return Integer; function Empty (Q : Queue) return Boolean; private type Queue is …; end Queues; package Queues is type Queue is limited private; procedure Add (Q : Queue; Element : Integer); function First (Q : Queue) return Integer; function Get (Q : Queue) return Integer; function Empty (Q : Queue) return Boolean; private type Queue is …; end Queues; operations values
76
76 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Objects & Variables OBJECT = instance of an ADT –piece of memory containing values of the ADT VARIABLE = name of a specific object not all objects have a name
77
77 http://libre.adacore.com © AdaCore under the GNU Free Documentation License My_Q : Queue; Memory Queue object an object name of the object
78
78 http://libre.adacore.com © AdaCore under the GNU Free Documentation License type Queue_Ptr is access Queue; Ptr : Queue_Ptr; Ptr := new Queue; Memory Queue object not all objects have a name object has no name Ptr is just a pointer to the object
79
79 http://libre.adacore.com © AdaCore under the GNU Free Documentation License CLASS = ADT + inheritance
80
80 http://libre.adacore.com © AdaCore under the GNU Free Documentation License operation Private data operation ENCAPSULATION invoke some operation in the SPEC to use ADT services
81
81 http://libre.adacore.com © AdaCore under the GNU Free Documentation License The object model
82
82 http://libre.adacore.com © AdaCore under the GNU Free Documentation License The C model C module GLOBAL DATA
83
83 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Programming in the Large –specification & implementation –privacy –abstract data types –hierarchical packages
84
84 http://libre.adacore.com © AdaCore under the GNU Free Documentation License To add functionality... package Queues is type Queue is private; procedure Add (Q : Queue; Element : Integer); function First (Q : Queue) return Integer; function Get (Q : Queue) return Integer; function Empty (Q : Queue) return Boolean; private type Queue is …; end Queues; function Last (Q : Queue) return Integer; Must add it to Queues Queue is a private type
85
85 http://libre.adacore.com © AdaCore under the GNU Free Documentation License But... Every time you change a spec you must recompile all its clients Every time you change a module you must RETEST the whole module
86
86 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Solution: use child units package Queues is type Queue is private; procedure Add (Q : Queue; Element : Integer); function First (Q : Queue) return Integer; function Get (Q : Queue) return Integer; function Empty (Q : Queue) return Boolean; private type Queue is …; end Queues; queues.ads function Queues. Last (Q : Queue) return Integer; queues-last.ads Child subprogram
87
87 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Child Units Rules The body or private part of a child unit can see the private part of all of its parents The spec of a child unit does NOT
88
88 http://libre.adacore.com © AdaCore under the GNU Free Documentation License Using a child unit with Queues; use Queues; with Queues.Last; procedure Client is Q : Queue; X : Integer; begin Add (Q, 123); Add (Q, 3); X := Queues.Last (Q); end Client;
89
89 http://libre.adacore.com © AdaCore under the GNU Free Documentation License package Queues is type Queue is private; procedure Add (Q : Queue; Element : Integer); function First (Q : Queue) return Integer; function Get (Q : Queue) return Integer; function Empty (Q : Queue) return Boolean; private type Queue is …; end Queues; queues.ads package Queues. New_Functionality is function Last (Q : Queue) return Integer; end Queues. New_Functionality queues-new_functionality.ads Child package
90
90 http://libre.adacore.com © AdaCore under the GNU Free Documentation License with Queues; use Queues; with Queues.New_Functionality; procedure Client is Q : Queue; X : Integer; begin Add (Q, 123); Add (Q, 3); X := Queues.New_Functionality.Last (Q); end Client;
Similar presentations
© 2024 SlidePlayer.com. Inc.
All rights reserved.