设计模式 1/3 -创建型设计模式






4.91/5 (238投票s)
在这篇关于设计模式的第一篇文章中,我将讨论创建型设计模式,并通过实际示例展示如何实现它们。
- 下载 Creational_StructuralExamples-noexe.zip - 24.9 KB
- 下载 Creational_StructuralExamples.zip - 73.2 KB
- 下载 Creational_RealWorldExamples-noexe.zip - 35.9 KB
- 下载 Creational_RealWorldExamples.zip - 100.9 KB
引言
在软件工程中,设计模式是指在软件设计问题中反复出现的一般可重用解决方案。模式不是可以立即转化为代码的最终设计。它们只是如何解决特定问题的正式描述或模板。模式是您必须自己实现的最佳实践。设计模式仅描述类和对象之间的交互,而不是整体软件架构的大规模问题。
设计模式是软件开发人员的强大工具,但您必须记住,它们不应被视为软件的规定性规范。理解每个模式的概念比记忆其具体的实现、类、方法和属性更为重要。
每个软件开发人员都应该知道如何恰当地应用这些模式。使用不恰当的设计模式可能会导致应用程序性能不佳,或者增加代码库的复杂性和可维护性。
总的来说,软件设计模式可以分为三类:创建型模式、结构型模式和行为型模式。
在本文中,我将讨论创建型设计模式。
什么是创建型设计模式?
在软件工程中,创建型模式是处理对象创建机制的设计模式,当基本形式的对象创建可能导致设计问题或增加代码库复杂性时使用。
抽象工厂
抽象工厂模式是一种设计模式,它允许在不指定所用具体类的情况下创建相关对象的组。一个工厂类会生成对象集。
抽象工厂模式是“四人帮”设计模式之一,属于创建型模式组。工厂创建不同类型的具体对象。工厂现在代表它可以创建的“一族”对象。由工厂创建的这一族对象在运行时根据具体工厂类的选择来确定。在这种情况下,客户端不知道它从每个工厂获得哪个具体对象,因为它只使用了其产品的通用接口。当您需要分离对象实例化的细节时,此模式非常有用。通常,工厂可能有一个以上的工厂方法。每个工厂方法都封装了 new 运算符以及具体的、平台特定的产品类。然后,每个平台都用一个派生的工厂类来建模。
结构代码示例

下面的 UML 图描述了抽象工厂设计模式的实现。该图包含五个类
- 客户端 (Client):此类使用
AbstractFactory
和AbstractProduct
类声明的接口。 - 抽象工厂 (AbstractFactory):这是所有将生成新相关对象的具体工厂类的抽象基类。对于将要实例化的每种类型的对象,此基类中都包含一个方法
- 具体工厂 (ConcreteFactory):此类继承自
AbstractFactory
类。ConcreteFactory
重写了AbstractFactory
的方法,这些方法将生成新的相关对象。如果AbstractFactroy
是一个接口,此类必须实现工厂接口的所有成员。 - 抽象产品 (AbstractProduct):此类是工厂可以创建的所有类型对象的基类。
- 具体产品 (ConcreteProduct):这是
AbstractProduct
类的具体实现。可以有多个从AbstractProduct
类派生的类,它们具有特定功能。
using System;
namespace AbstractFactory
{
static class Program
{
static void Main()
{
AbstractFactory factory1 = new ConcreteFactory1();
var client1 = new Client(factory1);
client1.Run();
AbstractFactory factory2 = new ConcreteFactory2();
var client2 = new Client(factory2);
client2.Run();
}
}
class Client
{
private readonly AbstractProductA _abstractProductA;
private readonly AbstractProductB _abstractProductB;
public Client(AbstractFactory factory)
{
_abstractProductB = factory.CreateProductB();
_abstractProductA = factory.CreateProductA();
}
public void Run()
{
_abstractProductB.Interact(_abstractProductA);
}
}
abstract class AbstractProductA
{
}
abstract class AbstractProductB
{
public abstract void Interact(AbstractProductA a);
}
class ProductA1 : AbstractProductA
{
}
class ProductB1 : AbstractProductB
{
public override void Interact(AbstractProductA a)
{
Console.WriteLine(GetType().Name +
" interacts with " + a.GetType().Name);
}
}
class ProductA2 : AbstractProductA
{
}
class ProductB2 : AbstractProductB
{
public override void Interact(AbstractProductA a)
{
Console.WriteLine(GetType().Name +
" interacts with " + a.GetType().Name);
}
}
abstract class AbstractFactory
{
public abstract AbstractProductA CreateProductA();
public abstract AbstractProductB CreateProductB();
}
class ConcreteFactory1 : AbstractFactory
{
public override AbstractProductA CreateProductA()
{
return new ProductA1();
}
public override AbstractProductB CreateProductB()
{
return new ProductB1();
}
}
class ConcreteFactory2 : AbstractFactory
{
public override AbstractProductA CreateProductA()
{
return new ProductA2();
}
public override AbstractProductB CreateProductB()
{
return new ProductB2();
}
}
}
真实世界示例
下面的示例演示了创建不同制造商的不同类型的汽车。我创建了一个名为 IVehicleFactory
的抽象工厂接口(抽象工厂)和两个具体工厂实现,分别称为 FordFactory
(具体工厂)和 MitsubishiFactory
(具体工厂)。IVehicleFactory
有三个方法,它们返回 Vehicle
(抽象产品)对象。Vehicle
类是所有具体产品的基类。
public interface IVehicleFactory
{
Vehicle CreateEconomyCar();
Vehicle CreateRacingCar();
Vehicle CreateSUV();
}
public abstract class Vehicle
{
public string Model { get; set; }
public string Engine { get; set; }
public string Transmission { get; set; }
public string Body { get; set; }
public int Doors { get; set; }
public List<string> Accessories = new List<string>();
public abstract void ShowInfo();
}
class FordFactory:IVehicleFactory
{
public Vehicle CreateEconomyCar()
{
return new FordFocus();
}
public Vehicle CreateRacingCar()
{
return new FordGT1();
}
public Vehicle CreateSUV()
{
return new FordExplorer();
}
}
public class MitsubishiFactory:IVehicleFactory
{
public Vehicle CreateEconomyCar()
{
return new MitsubishiI();
}
public Vehicle CreateRacingCar()
{
return new MitsubishiLancerEvoIX();
}
public Vehicle CreateSUV()
{
return new MitsubishiPajero();
}
}
public class FordExplorer:Vehicle
{
public FordExplorer()
{
Model = "Ford Explorer";
Engine = "4.0 L Cologne V6";
Transmission = "5-speed M50D-R1 manual";
Body = "SUV";
Doors = 5;
Accessories.Add("Car Cover");
Accessories.Add("Sun Shade");
}
public override void ShowInfo()
{
Console.WriteLine("Model: {0}", Model);
Console.WriteLine("Engine: {0}", Engine);
Console.WriteLine("Body: {0}", Body);
Console.WriteLine("Doors: {0}", Doors);
Console.WriteLine("Transmission: {0}", Transmission);
Console.WriteLine("Accessories:");
foreach (var accessory in Accessories)
{
Console.WriteLine("\t{0}", accessory);
}
}
}
...
Builder
建造者模式是一种设计模式,它允许通过使用正确的行动顺序来逐步创建复杂对象。构造由导演对象控制,导演对象只需要知道它要创建的对象类型。
建造者模式是另一个“四人帮”设计模式,属于创建型设计模式系列。建造者模式的目的是将复杂对象的构造与其表示分离开来。当需要创建的复杂对象由必须按相同顺序或使用特定算法创建的组成部分构成时,则使用此模式。
结构代码示例
下面的 UML 图描述了建造者设计模式的实现。该图包含四个类
- 产品 (Product):表示正在构建的复杂对象。
- 建造者 (Builder):这是所有建造者的基类(或接口),并定义了为正确创建复杂对象(产品)必须采取的步骤。通常,每一步都是一个由具体实现重写的抽象方法。
- 具体建造者 (ConcreteBuilder):提供建造者的实现。建造者是一个能够创建其他复杂对象(产品)的对象。
- 导演 (Director):表示控制用于创建复杂对象的算法的类。
static class Program
{
static void Main()
{
Builder b1 = new ConcreteBuilder1();
Director.Construct(b1);
var p1 = b1.GetResult();
p1.Show();
}
}
static class Director
{
public static void Construct(Builder builder)
{
builder.BuildPartA();
builder.BuildPartB();
}
}
abstract class Builder
{
public abstract void BuildPartA();
public abstract void BuildPartB();
public abstract Product GetResult();
}
class ConcreteBuilder1 : Builder
{
private readonly Product _product = new Product();
public override void BuildPartA()
{
_product.Add("Part A");
}
public override void BuildPartB()
{
_product.Add("Part B");
}
public override Product GetResult()
{
return _product;
}
}
class Product
{
private readonly List<string> _parts = new List<string>();
public void Add(string part)
{
_parts.Add(part);
}
public void Show()
{
Console.WriteLine("Parts:");
foreach (string part in _parts)
Console.WriteLine("\t"+part);
}
}
真实世界示例
现在让我们来看一个实际的例子。我选择了车辆制造作为实际示例。在这个例子中,我创建了一个名为 VehicleBuilder
的抽象类(建造者),它是所有具体建造者类(FordExplorerBuilder
和 LincolnAviatorBuilder
)的基类。这个类有一个 CreateVehicle
方法,它实例化类型为 Vehicle
(产品)的受保护的 _vehicle
字段。其他方法是抽象的,必须由具体的建造者实现来重写。这些方法设置 _vehicle
的属性。最后一个类 VehicleCreator
扮演导演的角色。它有一个接受 VehicleBuilder
参数的构造函数,以及一个 CreateVehicle
方法,该方法控制创建对象和填充其属性的步骤。GetVehicle
方法返回完全构建好的 Vehicle
对象。
class Program
{
static void Main()
{
var vehicleCreator = new VehicleCreator(new FordExplorerBuilder());
vehicleCreator.CreateVehicle();
var vehicle = vehicleCreator.GetVehicle();
vehicle.ShowInfo();
Console.WriteLine("---------------------------------------------");
vehicleCreator = new VehicleCreator(new LincolnAviatorBuilder());
vehicleCreator.CreateVehicle();
vehicle = vehicleCreator.GetVehicle();
vehicle.ShowInfo();
}
}
public abstract class VehicleBuilder
{
protected Vehicle _vehicle;
public Vehicle GetVehicle()
{
return _vehicle;
}
public void CreateVehicle()
{
_vehicle = new Vehicle();
}
public abstract void SetModel();
public abstract void SetEngine();
public abstract void SetTransmission();
public abstract void SetBody();
public abstract void SetDoors();
public abstract void SetAccessories();
}
...
class FordExplorerBuilder : VehicleBuilder
{
public override void SetModel()
{
_vehicle.Model = "Ford Explorer";
}
public override void SetEngine()
{
_vehicle.Engine = "4.0 L Cologne V6";
}
public override void SetTransmission()
{
_vehicle.Transmission = "5-speed M5OD-R1 manual";
}
public override void SetBody()
{
_vehicle.Body = "SUV";
}
public override void SetDoors()
{
_vehicle.Doors = 5;
}
public override void SetAccessories()
{
_vehicle.Accessories.Add("Car Cover");
_vehicle.Accessories.Add("Sun Shade");
}
}
...
class LincolnAviatorBuilder : VehicleBuilder
{
public override void SetModel()
{
_vehicle.Model = "Lincoln Aviator";
}
public override void SetEngine()
{
_vehicle.Engine = "4.6 L DOHC Modular V8";
}
public override void SetTransmission()
{
_vehicle.Transmission = "5-speed automatic";
}
public override void SetBody()
{
_vehicle.Body = "SUV";
}
public override void SetDoors()
{
_vehicle.Doors = 4;
}
public override void SetAccessories()
{
_vehicle.Accessories.Add("Leather Look Seat Covers");
_vehicle.Accessories.Add("Chequered Plate Racing Floor");
_vehicle.Accessories.Add("4x 200 Watt Coaxial Speekers");
_vehicle.Accessories.Add("500 Watt Bass Subwoofer");
}
}
...
public class VehicleCreator
{
private readonly VehicleBuilder _builder;
public VehicleCreator(VehicleBuilder builder)
{
_builder = builder;
}
public void CreateVehicle()
{
_builder.CreateVehicle();
_builder.SetModel();
_builder.SetEngine();
_builder.SetBody();
_builder.SetDoors();
_builder.SetTransmission();
_builder.SetAccessories();
}
public Vehicle GetVehicle()
{
return _builder.GetVehicle();
}
}
...
public class Vehicle
{
public string Model { get; set; }
public string Engine { get; set; }
public string Transmission { get; set; }
public string Body { get; set; }
public int Doors { get; set; }
public List<string> Accessories { get; set; }
public Vehicle()
{
Accessories = new List<string>();
}
public void ShowInfo()
{
Console.WriteLine("Model: {0}",Model);
Console.WriteLine("Engine: {0}", Engine);
Console.WriteLine("Body: {0}", Body);
Console.WriteLine("Doors: {0}", Doors);
Console.WriteLine("Transmission: {0}", Transmission);
Console.WriteLine("Accessories:");
foreach (var accessory in Accessories)
{
Console.WriteLine("\t{0}",accessory);
}
}
}
工厂方法
工厂方法模式是一种设计模式,它允许创建对象而不必在代码中指定对象的类型。工厂类包含一个允许在运行时确定创建类型的方法。
这是“四人帮”模式的第三个。它也属于创建型模式组。此模式也称为虚拟构造函数模式。工厂方法模式定义了创建对象的接口,并将类型的选择留给子类。工厂方法设计模式使设计更具可定制性,但只增加了一点复杂性。其他设计模式需要新的类,而工厂方法只需要一个新的操作。
结构代码示例
下面的 UML 图描述了工厂方法设计模式的实现。该图包含四个类
- 基础工厂 (FactoryBase):这是将返回新对象的具体工厂类的抽象类。在某些情况下,它可能是一个简单的接口,其中包含工厂方法的签名。此类包含返回
ProductBase
对象的FactoryMethod
。 - 具体工厂 (ConcreteFactory):表示工厂的具体实现。通常,此类会重写生成
FactoryMethod
并返回ConcreteProduct
对象。 - 基础产品 (ProductBase):这是由具体工厂创建的所有产品的基类。在某些情况下,它可能是一个简单的接口。
- 具体产品 (ConcreteProduct):这是
ProductBase
的具体实现。具体产品类可以包含特定功能。这些对象由工厂方法创建。
static class Program
{
static void Main()
{
FactoryBase factory = new ConcreteFactory();
ProductBase product = factory.FactoryMethod(1);
product.ShowInfo();
product = factory.FactoryMethod(2);
product.ShowInfo();
}
}
public abstract class FactoryBase
{
public abstract ProductBase FactoryMethod(int type);
}
public class ConcreteFactory : FactoryBase
{
public override ProductBase FactoryMethod(int type)
{
switch (type)
{
case 1:
return new ConcreteProduct1();
case 2:
return new ConcreteProduct2();
default:
throw new ArgumentException("Invalid type.", "type");
}
}
}
public abstract class ProductBase
{
public abstract void ShowInfo();
}
public class ConcreteProduct1 : ProductBase {
public override void ShowInfo()
{
Console.WriteLine("Product1");
}
}
public class ConcreteProduct2 : ProductBase {
public override void ShowInfo()
{
Console.WriteLine("Product2");
}
}
真实世界示例
在这个例子中,我再次选择了车辆制造的例子。我们有一个名为 IVehicleFactory
的接口,其中有一个 CreateVehicle
方法,它返回一个 Vehicle
对象。另外两个类 FordExplorerFactory
和 LincolnAviatorFactory
是该接口的具体实现。对于 FordExplorerFactory
,CreateVehicle
方法返回一个 FordExplorer
对象,该对象派生自抽象的 Vehicle
类并重写了 ShowInfo
方法。此方法仅显示有关车辆的信息。FordExplorerClass
有一个默认构造函数,该构造函数填充了此对象的属性。
对于 LincolnAviatorFactory
,情况略有不同。CreateVehicle
方法返回一个 LincolnAviator
对象,但该对象有一个带参数的构造函数,其值用于填充对象的属性。
此示例演示了如何使用不同的工厂来创建不同类型的对象。
class Program
{
static void Main(string[] args)
{
IVehicleFactory factory = GetFactory("FactoryMethodPattern.ConcreteFactories.LincolnAviatorFactory");
var lincolnAviator = factory.CreateVehicle();
lincolnAviator.ShowInfo();
factory = GetFactory("FactoryMethodPattern.ConcreteFactories.FordExplorerFactory");
var fordExplorer = factory.CreateVehicle();
fordExplorer.ShowInfo();
}
static IVehicleFactory GetFactory(string factoryName)
{
return Assembly.GetExecutingAssembly().CreateInstance(factoryName) as IVehicleFactory;
}
}
public interface IVehicleFactory
{
Vehicle CreateVehicle();
}
public abstract class Vehicle
{
public string Model { get; set; }
public string Engine { get; set; }
public string Transmission { get; set; }
public string Body { get; set; }
public int Doors { get; set; }
public List<string> Accessories = new List<string>();
public abstract void ShowInfo();
}
public class FordExplorerFactory:IVehicleFactory
{
public Vehicle CreateVehicle()
{
return new FordExplorer();
}
}
public class LincolnAviatorFactory:IVehicleFactory
{
public Vehicle CreateVehicle()
{
return new LincolnAviator("Lincoln Aviator",
"4.6 L DOHC Modular V8",
"5-speed automatic",
"SUV",4);
}
}
public class FordExplorer:Vehicle
{
public FordExplorer()
{
Model = "Ford Explorer";
Engine = "4.0 L Cologne V6";
Transmission = "5-speed M50D-R1 manual";
Body = "SUV";
Doors = 5;
Accessories.Add("Car Cover");
Accessories.Add("Sun Shade");
}
public override void ShowInfo()
{
Console.WriteLine("Model: {0}", Model);
Console.WriteLine("Engine: {0}", Engine);
Console.WriteLine("Body: {0}", Body);
Console.WriteLine("Doors: {0}", Doors);
Console.WriteLine("Transmission: {0}", Transmission);
Console.WriteLine("Accessories:");
foreach (var accessory in Accessories)
{
Console.WriteLine("\t{0}", accessory);
}
}
}
public class LincolnAviator:Vehicle
{
public LincolnAviator(string model, string engine, string transmission, string body, int doors)
{
Model = model;
Engine = engine;
Transmission = transmission;
Body = body;
Doors = doors;
Accessories.Add("Leather Look Seat Covers");
Accessories.Add("Chequered Plate Racing Floor");
Accessories.Add("4x 200 Watt Coaxial Speekers");
Accessories.Add("500 Watt Bass Subwoofer");
}
public override void ShowInfo()
{
Console.WriteLine("Model: {0}", Model);
Console.WriteLine("Engine: {0}", Engine);
Console.WriteLine("Body: {0}", Body);
Console.WriteLine("Doors: {0}", Doors);
Console.WriteLine("Transmission: {0}", Transmission);
Console.WriteLine("Accessories:");
foreach (var accessory in Accessories)
{
Console.WriteLine("\t{0}", accessory);
}
}
}
原型
原型模式是一种设计模式,用于通过复制或克隆现有对象的属性来实例化类。新对象是原型的精确副本,但允许修改而不改变原件。
这是另一个“四人帮”创建型模式。作为程序员,我敢肯定您多次使用 new 关键字创建对象,在某些情况下,当对象有很多必须填充的属性时,这非常乏味,而且您花费在此单一任务上的时间非常多。我认为您会同意我的说法,当今的编程都是关于成本和资源节省(尤其是时间)是一个大问题。有时您可以找到另一种使用现有对象实例化新对象的方法。这种对象创建称为克隆。当使用 new 关键字创建新对象效率低下时,这种做法非常有用。
当原始对象被克隆时,新对象是浅拷贝(有时称为深拷贝)。此副本会复制原始对象的所有属性和字段。如果属性是引用类型,则引用也会被复制。
结构代码示例
下面的 UML 图描述了原型设计模式的实现。该图包含两种类型的类
- 原型 (Prototype):表示可以克隆的对象(的抽象基类)。此类包含一个名为
Clone()
的虚拟方法,该方法返回原型对象。.NET 框架包含一个名为ICloneable
的接口,该接口创建一个与现有实例具有相同值的类的实例。 - 具体原型 (ConcretePrototype):此类继承自
Prototype
基类并包含附加功能。在此类中,也重写了Clone()
方法。
static class Program
{
static void Main()
{
ConcretePrototype prototype = new ConcretePrototype("1");
ConcretePrototype clone = (ConcretePrototype)prototype.Clone();
Console.WriteLine("Cloned: {0}", clone.Id);
}
}
abstract class Prototype
{
private readonly string _id;
protected Prototype(string id)
{
_id = id;
}
public string Id
{
get { return _id; }
}
public abstract Prototype Clone();
}
class ConcretePrototype : Prototype
{
public ConcretePrototype(string id)
: base(id)
{
}
public override Prototype Clone()
{
return (Prototype)MemberwiseClone();
}
}
真实世界示例
在此示例中,我创建了两个具体原型类:Commander 和 Infantry。这两个类都继承自 StormTrooper 类。StormTrooper 类有两个公共方法:FirePower 和 Armor,以及一个公共方法 Clone,该方法返回对象的浅拷贝。
class Program
{
static void Main()
{
var stormCommander = new Commander();
var infantry = new Infantry();
var stormCommander2 = stormCommander.Clone() as Commander;
var infantry2 = infantry.Clone() as Infantry;
if (stormCommander2 != null)
Console.WriteLine("Firepower: {0}, Armor: {1}",stormCommander2.FirePower,stormCommander2.Armor);
if (infantry2 != null)
Console.WriteLine("Firepower: {0}, Armor: {1}", infantry2.FirePower, infantry2.Armor);
}
}
public abstract class StormTrooper:ICloneable
{
public int FirePower { get; set; }
public int Armor { get; set; }
public object Clone()
{
return MemberwiseClone();
}
}
public class Commander:StormTrooper
{
public Commander()
{
Armor = 15;
FirePower = 20;
}
}
public class Infantry : StormTrooper
{
public Infantry()
{
FirePower = 10;
Armor = 9;
}
}
单例
单例模式是一种设计模式,用于确保类只能有一个并发实例。每当需要单例类的其他对象时,都会提供先前创建的单个实例。
在某些情况下,拥有一个具体类的唯一实例非常重要。尤其是在您只需要对有限资源的单个全局访问点,并且创建将被复制的全局变量可能导致多个访问点并引起一些数据不一致的情况下。在 ASP.NET 中,HttpContext
类是单例的一个很好的例子。
单例模式的基本思想是集中管理内部或外部资源,并提供对这些资源的全局访问点。
结构代码示例
下面的 UML 图描述了单例设计模式的实现。该图仅包含一个类
- 单例 (Singleton):此类中有一个名为
GetSingleton
的静态方法,该方法返回私有变量中保存的单个实例。
static class Program
{
static void Main()
{
var singleton1 = Singleton.GetSingleton();
var singleton2 = Singleton.GetSingleton();
Console.WriteLine(singleton1==singleton2);
}
}
public sealed class Singleton
{
private static volatile Singleton _instance;
private static readonly object _lockThis = new object();
private Singleton() { }
public static Singleton GetSingleton()
{
if (_instance == null)
{
lock (_lockThis)
{
if (_instance == null) _instance = new Singleton();
}
}
return _instance;
}
}
真实世界示例
在此示例中,我创建了一个名为 ApplicationState 的单例类。此类有两个公共属性:LoginId
和 MaxSize
,它们代表应用程序的当前状态。为了确保此状态仅由单个实例持有,该类使用了单例设计模式。
当您创建一个需要某些单例的多线程应用程序时,您必须确保您的单例是线程安全的。这可以通过在 GetSingleton
方法中返回对象的地方使用 lock 子句来实现。
class Program
{
static void Main()
{
var state = ApplicationState.GetState();
state.LoginId = "kanasz";
state.MaxSize = 1024;
var state2 = ApplicationState.GetState();
Console.WriteLine(state2.LoginId);
Console.WriteLine(state2.MaxSize);
Console.WriteLine(state == state2);
}
}
public sealed class ApplicationState
{
private static volatile ApplicationState _instance;
private static object _lockThis = new object();
private ApplicationState() { }
public static ApplicationState GetState()
{
if (_instance == null)
{
lock (_lockThis)
{
if (_instance == null) _instance = new ApplicationState();
}
}
return _instance;
}
// State Information
public string LoginId { get; set; }
public int MaxSize { get; set; }
}
额外资源
以下是我想建议您查看的附加资源列表
- http://www.blackwasp.co.uk/Default.aspx
- http://www.dofactory.com/Patterns/Patterns.aspx
- http://www.go4expert.com/forums/showthread.php?t=5127
历史
- 7月30日 - 发布原始版本
- 8月8日 - 更新了单例模式 - 已实现双重检查