Tải bản đầy đủ (.pdf) (10 trang)

Building some design patterns in C#

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (306.85 KB, 10 trang )

Nguyễn Mạnh Đức

Tạp chí KHOA HỌC & CÔNG NGHỆ

90(02): 77 - 86

BUILDING SOME DESIGN PATTERNS IN C#
Nguyen Manh Duc*
College of Education – TNU

SUMMARY
In software engineering, design is a total solution for common problems in software design.
Profound idea of the design is good saving solutions-oriented design and object re-use them to
solve similar problems. New object oriented language C#, Microsoft's influencemany of the
languages Java and C++. But there are still many new and interesting featureis introduced to
simplify the design of object-oriented. In this article we will consider building a number of designs
that Gamma has proposed language with C#.The purpose of the work here is to have an
insight than some designs can be implemented in C#, to learn the new features of the language in
practice and make it easier for designers object-oriented software.
Key words: Design Patterns, Singleton, Proxy, Abstract Factory, Strategy, UML

INTRODUCTION*
Object oriented programming has been the
dominating style in software development for
quite a few years. The intuitive way in which
object oriented languages allow us to divide
our code into objects and classes is what
makes this style of programming attractive.
Another aim of object oriented program
design is to make code more reusable.
However practical has shown that designing


reusable object oriented software is not
always that easy. A good software design
should, not only solve existing problems, but
also concern future problems. It should make
the program flexible, easy to maintain and to
update. Design patterns help us address these
issues. The idea is quite simple, we save
document and design solutions that have been
used and worked for reoccurring problems, in
order to use them again in similar situations.
In software engineering, a design pattern is a
total solution for common problems in
software design. A design pattern is not a
perfect design for can be converted directly
into code, it is only a framework describing
how to solve a problem that can be reused in
many different situations. The object oriented
design patterns typically show relationships
and interactions between classes or objects,
*

Tel: 0915 564 249; Email:

without specifying the class or object of
each specific application.
The design can help speed up the process of
software
development
by providing
development patterns have been authenticated

and verified. It provides general solutions,
documented in a format that is not attached to
a particular problem. The model allows
developers to communicate with each other
using the name easy to understand, is widely
used to set for the interaction of software [1].
The aim of this work is to take a closer look
at how some of the known design patterns can
be implemented in C#, and to investigate
whether the new features of the language in
fact do make it easier to design object
oriented software.
After the introduction, section 2 will review
some of the designs represent the Gamma and
colleagues have proposed [1], we propose
how to design and build these templates in
C# and apply them in some real-world
problems; section 3 discusses some issues of
interest; Finally section 4 will include a
number of conclusions and future work.
DESIGN SOME PATTERNS IN C#
The designs are important building blocks for
design and modeling applications on all
platforms. The design helps us to
understand, discuss and re-use of applications
on aparticular
platform. In [1] Gamma and
77

Số hóa bởi Trung tâm Học liệu – Đại học Thái Nguyên





Nguyễn Mạnh Đức

Tạp chí KHOA HỌC & CÔNG NGHỆ

colleagues proposed 23 designs, they are
classified into 3 types are: creational patterns,
structural
patterns
and
behavioral
patterns. Here we will review a representtative sample for each type.
Creational Patterns
Creational pattern as the name implies are
concerned with the creation of object. The
patterns help you build a part of an
application that hides how an object is created
and composed from the representation of the
object. The only information known of the
object is its interface. Creational patterns can
be divided in class creational patterns and
object creational patterns. The difference lies
in that the class creational patterns use
inheritance to instantiate a class, while object
creational patterns delegate the instantiation
to another object.
Here we will look at the Singleton and

Abstract Factory patterns represent creational
patterns.
Singleton Pattern
Singleton design pattern is designed to ensure
that a class can only create a unique show and
it provides access points in a uniform global.
In fact, there are many situations where we
want a class has only a single show during
application execution. Singleton design
pattern has
been
born to
meet
the problems of this type.
The structure of the Singleton pattern is
represented by the UML diagram shown in
Figure 1 [1].
Singleton
static instance()
Singleton Operation()
GetSingletonData()

Return
uniqueInstance

static uniqueInstance
singletonData

Figure 1: Structure of the Singleton pattern


90(02): 77 - 86

Installation: Singleton design pattern can be
installed in the C# language as follows:
class Singleton
{
// Field
private static Singleton instance;
// Constructor
protected Singleton() { }
// Method
public static Singleton Instance()
{
if (instance == null)
instance = new Singleton();
return instance;
}
}

Apply the Singleton pattern:
Problem 1: Suppose the need to build a Find
dialog box and search for a text editor. Note
that in all cases to ensure that only up to a
Find dialog box appears for convenience to
users and for the management of the system [1].
The most reasonable solution for the above
case is designed for that class can express
itscontrol. Specifically, the class will always
ensure that no other show can be created
from it.

Normally when an
object to be
created from
the class must
be created
through function (constructor) of that class.
Therefore, to prevent the user create objects
freely, we will prevent the creation function
can be called, by turning it into a private
domain (or protected) in the class. Then
provide a static method, to create new objects
in a controlled, iecheck if the object will
not create new objects, otherwise it returns
the object already exist. This solution will
ensure only one instance of the class only.
The clients to access instance of class through
the constructor mentioned above. Thus, the
Find dialog box can be installed as follows:
public class Find
{
protected static Find instance = null; // field
protected Find() {} //constructor
public static Find Singleton() //method

the Singleton pattern.
78

Số hóa bởi Trung tâm Học liệu – Đại học Thái Nguyên





Nguyễn Mạnh Đức

Tạp chí KHOA HỌC & CÔNG NGHỆ

return ServerName;
}
public static DataInfo Singleton()
{
if (instance == null)
instance = new DataInfo("DHTN",
"data2011", "Le An", "an-dhtn");
return instance;
}

{
if (instance == null) instance = new Find();
return instance;
}
}

If a user creates an instance of the class with
the command: Find find
It will have compile errors, so constructor of
the class is located in southern private
(protected). Instance of the class can only
be made by:
Find find = Find.Singleton();


Therefore, we can completely control the
instance of the class created in the method
Singleton(), ensuring that only one the an
object.
Problem 2: Another application is a class that
can be designed for its information accessed
globally by
all other
objects in
the
program. For example, sometimes we need a
singleglobal
object store
information
connected to the database or information
through computer
networks connected to
other objects can be accessed as needed to
produce this singleobject [1]. Specifically, we
need to store information about a database
include:
ServerName,
DatabaseName,
UserName and Password. Need to access the
information at any time and anywhere in
the program. We can use the Singleton pattern
solve this problem as follows:
public class DataInfo
{
private string ServerName;

private string DatabaseName;
private string UserName;
private string Password;
protected static DataInfo instance = null;
protected DataInfo(string servername,
string databasename,
string username,
string password)
{
ServerName = servername;
DatabaseName = databasename;
UserName = username;
Password = password;
}
public string getServerName()
{

90(02): 77 - 86

}
class Program
{
static void Main(string[] args)
{
DataInfo dataInfo = DataInfo.Singleton();
Console.WriteLine("ServerNamme:"+
dataInfo.getServerName());
}
}


Abstract Factory Pattern
Abstract Factory is a design pattern provides
to the client program an interface , which is a
family or a set of objects of different classes
have the same interface with each other,
which is not directly work with each subclass
particular.
Abstract Factory design pattern to pack a
group of the class acts as a "production"
(factory) in the application, this is the class
used to create objects. The production of this
the class have a common programming
interface that is inherited from a parent class
pure virtual called class "virtual factory
class"
Structure of the Abstract Factory pattern is
represented by the UML diagram in Figure 2
[1]. Of which:
 ConcreteFactory: real-time methods in
AbstractFactory to
create
the specific
object. The system has many objects,
the groups generated by the ConcreteFact-ory
similar role.
 AbstractFactory: is abstract, it specifies
the interface for manipulating create
"products" virtual (AbstractProduct).
 Product: As the reality of
object

generated from the concreteFactory class
and it inherited the class virtual product.
79

Số hóa bởi Trung tâm Học liệu – Đại học Thái Nguyên




Nguyễn Mạnh Đức

Tạp chí KHOA HỌC & CÔNG NGHỆ

90(02): 77 - 86

Figure 2: Structure of the Abstract Factory pattern

AbstractProduct.
 Client: Is a program to create objects in
the system, it uses AbstractFactory and
AbstractProduct.
Building Abstract Factory Pattern in C#:
Imagine you are designing software to build
an electric motor. First you must determine
the AbctracFactory and AbstractProduct
classes . We called them DongCoFactory
and DongCo, these classes are installed in
C# as follows:
abstract class DongCoFactory
{

public abstract DongCo getDongCo();
}
abstract class DongCo
{
public abstract string type { get; }
}

Now we

define

a product

class

DongCoE500Product
for
DongCo
class,
ConcreteFactory class for DongCoFactory:
class DongCoE500Product : DongCo
{
string _type = "DCE-500";
public override string type
{
get { return _type; }
}
}
class concreteDongCoFactory1 : DongCoFactory
{


public override DongCo getDongCo()
{
return new DongCoE500Product();
}
}

We now have a factory and the product, ready
for the implementation of theclasses and
build the engines. But first we need a client
and a Main class. Here, the client class is
testDriver.

class TestDriver
{ public void Model(DongCoFactory factory)
{
DongCo dc = factory.getDongCo();
Console.WriteLine("Model {0}", dc.type);
}
}
class Program
{
static void Main(string[] args)
{
DongCoFactory factory1 =
new concreteDongCoFactory1();
new TestDriver().Model(factory1);
Console.ReadLine();
}
}


Now, to add another factory class and other
types of DongCo can be made as follows:
class DongCoE700Product: DongCo
{ string _type = "DCE-700";

80

Số hóa bởi Trung tâm Học liệu – Đại học Thái Nguyên




Nguyễn Mạnh Đức

Tạp chí KHOA HỌC & CÔNG NGHỆ

public override string type
{
get { return _type; }
}
}
class concreteDongCoFactory2 : DongCoFactory
{
public override DongCo getDongCo()
{
return new DongCoE700Product();
}
}


Now we add code to the Main function as
follows, and then implement programs to
presentperformance results, and note that
the client class is that testDriver not remain
unaltered.
class Program
{
static void Main(string[] args)
{
DongCoFactory factory1 =
new concreteDongCoFactory1();
new TestDriver().Model(factory1);
DongCoFactory factory2 =
new concreteDongCoFactory2();
new TestDriver().Model(factory2);
}
}

The above example shows that how to add
a factory and a new product into the system.
Structural Patterns
Structural patterns suggest ways to put
together existing objects into complex
structures, in order to achieve new

90(02): 77 - 86

functionality. Class structural patterns use
inheritance to compose interfaces and
implementation, whereas in object structural

patterns an object can reside inside another
object. In this section, we will review a
representative sample for this model, which is
the proxy pattern.
Proxy Pattern:
Proxy pattern is used when the need to
replace, or
more
accurately represent a
complex object with a simple object. If the
initialization of an object that consuming too
many resources or time, the Proxy pattern is a
good solution, it allows only if clearly needed
a newinitialized object. Proxy now can be
applied anywhere where you need to have an
objectreference to a more flexible, more
sophisticated than using a simple pointer.
The structure of the Proxy pattern is presented
in Figure 3 [1]. Of which:
 Subject: A pure virtual object class or
interface, this class can be inherited and
implemented by Proxy and other objects.
 RealSubject: Inherit from the Subject, its
full implementation or expansion functions
defined in the Subject.
 Proxy: Inherits from Subject, it is
responsible for transfer function calls
(requests) from the Subject to RealSubject
whenever necessary.


Client

Figure 3: Structure of the Proxy pattern

81

Số hóa bởi Trung tâm Học liệu – Đại học Thái Nguyên




Nguyễn Mạnh Đức

Tạp chí KHOA HỌC & CÔNG NGHỆ

Building Proxxy pattern in C#:
The following example will illustrate the
installation of Proxy pattern in C#. Suppose
weneed to build a service processor in a
computing system, we can build this service
form Proxy pattern as follows:
First define an delegate Calculate as follows:
public delegate int Calculate(int a, int b);

Then there
Subject:

is

an interface IMath


role for

public interface IMath
{
int ADD(int x, int y);
int SUB(int x, int y);
}
And next is a Math class is inherited from the
MarshalByRefObject

class IMath and the

system

acts as RealSubject:
class Math: MarshalByRefObject, IMath
{
public int ADD(int x, int y) { return x + y; }
public int SUB(int x, int y) { return x - y; }
}

inherited from class IMath:
class MathProxy: IMath
{
Math math = new Math();
public MathProxy()
{
Console.WriteLine("Calculator constructor");
// Tao the hien cua Math trong mot

// AppDomain khac
AppDomain ad =
System.AppDomain.CreateDomain("MathDomain",

null, null);
}
public int ADD(int x, int y)
{
return math.ADD(x, y);
}
public int SUB(int x, int y)
{
return math.SUB(x, y);
}
}

This class will change the calculations
required to handle RealSubject class when
needed.

90(02): 77 - 86

Next we add the Client class, this class has
two methods: Xuly used delegate packaging
methods calculation processing services and
Display to create
Proxy objects and call
handling requirements necessary calculations:
public class Client
{

int x = 12, y = 7;
void Xuly(Calculate cal, int v1, int v2)
{
Console.Write("\nXu ly voi:{0}; {1}: ",v1,v2);
Console.WriteLine("Ket qua={0}",cal(v1, v2));
}
public void Display()
{
MathProxy mp = new MathProxy();
Calculate pt = new Calculate(mp.ADD);
Xuly(pt, x, y);
pt = mp.SUB;
Xuly(pt, x, y);
}
}

In illustration of this Proxy application
pattern, we have used Imath interface to
determine theservice requirements, delegate
technicians to
perform
the calculations
required and theprocess of creating objects in
C#
language
instead
of having
to
use ordinary pointers as in C++, to save
resources and ease of handling.

Behavioural Patterns
Behavioral
patterns focused
on problem
solving algorithm and the division of
responsibilities between objects. Behavior
patterns are most concerned to the
transmission of communications between
objects. Behavioral
patterns not
only describes
the
object
model
of the model but also describes the exchange
of information between them; characterize
complex control flow, enabling us to focus
more on how to build links between objects
instead of the control flow. Here we consider
the strategy pattern represent for behavioral
patterns.
Strategy pattern:
The Strategy pattern defines a family of
algorithms,
encapsulates
the
related
algorithms and makes them interchangeable.
This allows the selection of algorithm to vary


82

Số hóa bởi Trung tâm Học liệu – Đại học Thái Nguyên




Nguyễn Mạnh Đức

Tạp chí KHOA HỌC & CÔNG NGHỆ

independently from clients that use it and
allows it to vary over time [4].
The structure of the Strategy pattern as shown
in Figure 4 [1], in which:
 Strategy: Defining the interface for all
classes
present
algorithm. During the
initialization process may object to add
data from the Context.
 ConcreteStrategy: Is the implementation
of the Strategy interface to present a specific
algorithm.
 Context: At compile time, only to use the
object Strategy model when determining
thealgorithm for the problem to be treated; At
the time of execution given a specific object
of algorithm replacement for object Strategy.
Building Strategy pattern in C#:

The construction behind the Strategy pattern
is to encapsulate the number of strategies in a
single module and provide an uncomplicated
interface to allow the clients to choose
between these strategies. If you have several
different behaviours that you want an object
to perform, it is much simpler to keep track of
them if each behaviour is a separate class,
instead of the most common approach of
putting them in one method. This is illustrated
in figure 4 [1]. By doing this you can easily
add, remove, or change the different
behaviours, since each one is its own class.
Each such behaviour or algorithm
encapsulated into its own class is called a
Strategy. The strategies do not need to be
members of the same class hierarchy but they
do have to implement the same interface [2].
The new language support for interfaces in
C# comes in handy when implementing the
Strategy pattern. C++ programmers typically
create interfaces by defining abstract classes
with pure virtual methods. In C#, all interface

90(02): 77 - 86

members are public, and classes adhering to
an interface must implement all methods in
the interface.
The following code demonstrates the Strategy

pattern, which encapsulates functionality in
the form of an object. This basic example is
based on the structure in figure 4.
First we create an Strategy interface with its
method are AlgorithmInterface():
interface Strategy
{
void AlgorithmInterface();
}

Next is subclass of concreteStrategy inherits
the Strategy interface, which corresponds
to thealgorithm of each case will be selected
strategy:
class ConcreteStrategyA : Strategy
{
public void AlgorithmInterface()
{
Console.WriteLine(this.ToString());
}
}
class ConcreteStrategyB : Strategy
{
public void AlgorithmInterface()
{
Console.WriteLine(this.ToString());
}
}
class ConcreteStrategyC : Strategy
{

public void AlgorithmInterface()
{
Console.WriteLine(this.ToString());
}
}

Figure 4: Structure of the Strategy pattern

83

Số hóa bởi Trung tâm Học liệu – Đại học Thái Nguyên




Nguyễn Mạnh Đức

Tạp chí KHOA HỌC & CÔNG NGHỆ

Next is the Context class will provide specific
algorithm objects as required:
class Context
{
private Strategy strategy;
public Context(Strategy strategy)
{
this.strategy = strategy;
}
public void ContextInterface()
{

strategy.AlgorithmInterface();
}
}

Finally, the class that contains
function to execute the program:

the Main

class Program
{
static void Main(string[] args)
{
Context cA = new Context(
new ConcreteStrategyA());
cA.ContextInterface();
Context cB = new Context(
new ConcreteStrategyB());
cB.ContextInterface();
Context cC = new Context(
new ConcreteStrategyC());
cC.ContextInterface();
}
}

DISCUSSION
A design pattern is a description of a set of
interacting classes that provide a framework
for a solution to a generalized problem in a
specific context or environment. In other

words, a pattern suggests a solution to a
particular problem or issue in object-oriented
software development.
In today’s software development, applications
and systems are complex. These products
require a great deal of flexibility in design and
architecture to accommodate the everchanging needs of clients and users during the
product development and also after the
product has been released. Design patterns
assist in laying the foundation for a flexible

90(02): 77 - 86

architecture, which is the characteristic of
every good object-oriented design [5, 6].
C# together with .NET brings about many
benefits, including the easy-to-use object
model, the garbage collection mechanism for
automatically cleaning up resources, and far
improved libraries covering areas ranging
from Windows GUI support to data access
and generating web pages. The .NET
framework insures that enough information is
included in the compiled library files (the
assemblies) and that your classes can be
inherited from and used by other .NET-aware
code without requiring access to your source
files [3, 4].
The record in fact shows that pointers is one
of the largest sources causes errors when

developing software, the concept of pointers
in C and C++ does not exist in C #. However,
it can overcome this problem in C# using
other mechanisms such as: set the reference
variable with the parameters of the method,
or by the constructor the object classes [4]…
An interface is basically a contract between a
class and a client that guaranties that the class
implements the methods specified in the
interface. In other words, interfaces contain
the public signature of methods, events and
properties but it is up to the class, which is
said to implement the interface, to provide the
implementation of these methods. The
implementing class inherits from an interface
in the same way as from a base class. An
instance of this class can then be casted into
the interface and access its methods. In this
way interfaces can be used to reach the same
objectives as with multiple inheritance [5, 7].
As Liberty [2] states, in the programming
language C#, delegates are first-class objects,
fully supported by the language. Using a
delegate allows the programmer to
encapsulate a reference to a method inside a
delegate object. You can encapsulate any
matching method in that delegate without
having to know at compile time which
method that will be invoked. A delegate in C#
is similar to a function pointer in C or C++.


84

Số hóa bởi Trung tâm Học liệu – Đại học Thái Nguyên




Nguyễn Mạnh Đức

Tạp chí KHOA HỌC & CÔNG NGHỆ

But unlike function pointers, delegates are
object-oriented,
type-safe
and
secure
managed objects.
One of the primary goals of C# is safety;
many of the problems that programmers can
cause in C and C++ are avoided in C#.
Generally
the
abstraction
level
of
programming is higher in C# compared to
C++. Introducing C# in design patterns
provides the programmer with a modern
object-oriented

programming
language
offering syntactic constructs and semantic
support for concepts that map directly to
notions in object-oriented design.
The designs show that you always have a
program with an interface does not enforce it.
Then, in all inherited classes, you will be
more flexible implementation of the method
is most suitable to your purpose. Since C#
supports interface, this feature is useful when
implementing patterns as Adapter, Strategy,
Abstract Factory ... The technical delegate an
dother events also match the features of the
design and for the design of the patterns
becomes easier.
CONCLUSION
In software engineering, design is a total
solution for common
problems in software
design. Profound idea of the design is good
saving solutions-oriented design and object
re-use them to solve similar problems.
New
object-oriented language
C# is
Microsoft's influencemany of the languages
Java and C++. But there are still many new
and exciting features are introduced to
simplify object-oriented design. In this article

we will consider building designs which
some have proposed Gamma
by C#
language, namely the Singleton pattern,
AbstractFactory, Proxy and Strategy. These
models are built based on some new features
of C #,
such
as creating
a single
object using the static method, the use of
abstract classes to define the interface for
manipulating create "products" virtual;
techniques authorized to perform the

90(02): 77 - 86

required processing
requirements
and
mechanism for creating objects in C# instead
of using pointers to normal C++ and use
this technique class inheritance and interfaces
instead of multiple inheritance in C++ to
implement multi-processing forms...
Object oriented language C#, Microsoft's
influencemany of the languages Java and
C++. But there are still many new and
interesting featureis introduced to simplify
the design of

object-oriented. In
this
article we will consider building a number
of designs
that Gamma has
proposed
language with C#. The purpose of the
work here is to have an insight than some
designs can
be implemented
in C#, to
learn the new features of the language in
practice
and
make it
easier
for
designers object-oriented software.
In the
future
we will
look
at other patterns and build on the languages
C# or Java. Research
on the refine designs. Application forms and
combine them to solve the problem in
practice.
REFERENCES
[1]. Gamma E., et. al, Elements of Reusable
Object-Oriented Software, Addison-Wesley. The

PDF conversion was made in February 2003.
[2]. Liberty J., Programming C#, 2nd Edition,
O’Reilly, ISBN: 0-596-00309-9, 2002.
[3]. MSDN, C# Language specification 17.
Attributes, Microsoft Corporation 2004.
[4]. Mathias Bartoll, Nori Ahari, Oliver C.Moldez,
Design patterns in C#, Mälardalen University
Västerås, Sweden. 2004.
[5]. Sherif M. Yacoub, H.H. Ammar, PatternOriented Analysis and Design: Composing
Patterns to Design Software Systems, Addison
Wesley 2003.
[6]. Nguyen
Manh Duc
(2011),
"Some
calculations
and
refinements with the
components",
Journal
of
Science
and
Technology, University of Thai Nguyen,
volume 78, No. 02, p. 97-104
[7]. Pham Huu Khang, Tran Tien Dung, C# 2005
Object Oriented Programming, Publishing
House of Social Labour, 2008.

85


Số hóa bởi Trung tâm Học liệu – Đại học Thái Nguyên




Nguyễn Mạnh Đức

Tạp chí KHOA HỌC & CÔNG NGHỆ

90(02): 77 - 86

TÓM TẮT
XÂY DỰNG MỘT SỐ MẪU THIẾT KẾ TRONG C#
Nguyễn Mạnh Đức*
Trường Đại học Sư phạm – Đại học Thái Nguyên

Trong công nghệ phần mềm, mẫu thiết kế là một giải pháp tổng thể cho các vấn đề chung
trong thiết kế phần mềm. Ý tƣởng sâu xa của các mẫu thiết kế là để tiết kiệm tốt các giải pháp thiết
kế hƣớng đối tƣợng và việc tái sử dụng chúng để giải quyết các vấn đề tƣơng tự. Ngôn ngữ hƣớng
đối tƣợng mới C# của hãng Microsoft có ảnh hƣởng nhiều từ các ngôn ngữ Java và C++, tuy nhiên
vẫn còn nhiều tính năng mới và thú vị đƣợc giới thiệu để đơn giản hóa thiết kế hƣớng đối tƣợng.
Trong bài báo này chúng tôi sẽ xem xét xây dựng một số mấu thiết kế mà Gamma đã đề xuất bằng
ngôn ngữ C#. Từ đó có một cái nhìn sâu sắc hơn một số mẫu thiết kế có thể thực hiện trong C#,
tìm hiểu các tính năng mới của ngôn ngữ trong thực tế và làm cho dễ dàng hơn trong thiết kế phần
mềm hƣớng đối tƣợng.
Từ khóa: Các mẫu thiết kế, mẫu Singleton, mẫu Proxy, mẫu Abstract Factory, mẫu Strategy, ngôn
ngữ mô hình hóa thống nhất.

*


Tel: 0915 564 249; Email:

86

Số hóa bởi Trung tâm Học liệu – Đại học Thái Nguyên





×