几种常见设计模式的总结

Posted reecelin

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了几种常见设计模式的总结相关的知识,希望对你有一定的参考价值。

几种常见的设计模式

1. 单例模式

 单例模式是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

注意:

  • 单例类只能有一个实例;

  • 单例类必须自己创建自己的唯一实例;

  • 单例类必须给所有其他对象提供这一实例;

意图:单例模式可以保证一个类仅有一个实例,并提供一个它的全局访问点。

主要解决:一个全局使用的类频繁地创建与销毁;

何时使用:想控制实例数目,节省系统资源的时候;

如何判断:判断系统是否已经有这个实例,若无则创建,若有则直接返回;

关键代码:构造函数私有;

 

实现:

技术图片

1.1 饿汉式

 懒汉式即加载单例类时直接创建一个实例,然后将这个实例返回出去。由于饿汉式是基于classLoader机制避免了多线程的同步问题,所以它是线程安全的。由于没有加锁,所以它的执行效率很高,但在类加载时就初始化,浪费内存。

public class SingletonModelHungry{
   public static SingletonModelHungry instance=new SingletonModelHungry();
   
   private SingletonModelHungry(){
       
  }
   
   public static SingletonModelHungry getInstance(){
       return instance;
  }
}

1.2 懒汉式

 懒汉式具备lazy loading特性,在使用到的时候才会去创建,减少内存的浪费。这是最基本的实现方式,但是最大问题是不支持多线程。因为没有加锁synchronized,所以严格意义上它并不算是单例模式。这种方式lazy loading很明显,不要求线程安全,不能在多线程环境下工作。

 

public class SingletonModelLazy {

   private static SingletonModelLazy singleTonModel;

   private SingletonModelLazy(){

  }

   public static SingletonModelLazy getInstance(){
       if (singleTonModel==null){
           singleTonModel=new SingletonModelLazy();
      }
        return singleTonModel;
  }
}

1.3 双重校验锁(double-checked locking)

 这种方式采用双锁机制,安全且在多线程情况下能保持高性能。

public class DoubleChekcedLocking{
   public volatile static DoubleCheckedLocking instance;
   
   private DoubleChekcedLocking(){
       
  }
   
   public static DoubleChekcedLocking getInstance(){
       if(instance ==null){
           synchronized(DoubleChekcedLocking.class){
               if (instance==null){
                   instance=new DoubleChekcedLocking();
              }
          }
      }
       return instance;
  }
}

1.4 静态内部类

 适用于静态域的情况,此种方式能达到与双重校验一样的效果,但实现更简单。这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟第 3 种方式不同的是:第 3 种方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比第 3 种方式就显得很合理。

public class SingletonInnerClass{
   
   private static class Inner{
       
       private static final SingletonInnerClass INSTANCE =new SingletonInnerClass();
  }
   
   private SingletonInnerClass(){
       
  }
   
   public static SingletonInnerClass getInstance(){
       retrun Inner.INSTANCE;
  }
}

1.5 枚举

此种方式是自jdk1.5加入enum之后引入的,这种方式是实现单例模式的最佳方法。它更简洁,不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化,但不能通过 reflection attack 来调用私有构造方法。

public enum SingletonEnum{
   INSTANCE;
}

 

2. 工厂模式

 工厂模式是java中最常用的设计模式之一。它提供了一种创建对象的最佳方式,在工厂模式中创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

意图:定义一个创建对象的接口,让其子类决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行;

主要解决:主要解决接口选择问题;

何时使用:在不同条件下创建不同的实例;

如何解决:让其子类实现工厂接口,返回一个抽象的产品;

关键代码:创建过程在子类进行;

注意事项:作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。但对于简单对象,只需要通过new就可以完成创建的对象,无需使用工厂模式,否则会增加系统的复杂度。

 

实现:

 先创建一个Shape接口和实现该接口的实体类,再定义一个工厂类ShapeFactory,使用工厂来获取Shape对象。

技术图片

Shape

public interface Shape{
   void draw();
}

Circle

public class Circle implements Shape{
   @Override
   public void draw(){
       System.out.println("this is circle");
  }
}

Rectangle

public class Rectangle{
   
   @Override
   public void draw(){
       System.out.println("this is rectangle");
  }
}

 

ShapeFactory

public class ShapeFactory{
   

   private Shape shape;
 
 public Shape getShape(String type){
     if(type==null || Objects.equals(type,"")){
         return null;
    }
     if( Objects.equals(type,"CIRCLE")){
         shape=new Circle();
    }
     
     if( Objects.equals(type,"RECTANGLE")){
         shape=new Rectangle();
    }
     return shape;
}
}

Test

public class Test{
   public static void main(String[] args){
       ShapeFactory shapeFactory=new ShapeFactory();
       Shape circle=shapeFactory.getShape("CIRCLE");
       Shape rectangle=shapeFactory.getShape("RECTANGLE");
       circle.draw();
       rectangle.draw();
  }
}

控制台输出:

this is circle
this is rectangle

3. 抽象工厂模式

抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象。

意图:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

主要解决:主要解决接口选择的问题。

何时使用:系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。

如何解决:在一个产品族里面,定义多个产品。

关键代码:在一个工厂里聚合多个同类产品。

实现:

我们将创建 Shape 和 Color 接口和实现这些接口的实体类。下一步是创建抽象工厂类 AbstractFactory。接着定义工厂类 ShapeFactory 和 ColorFactory,这两个工厂类都是扩展了 AbstractFactory。然后创建一个工厂创造器/生成器类 FactoryProducer。使用 FactoryProducer 来获取 AbstractFactory 对象。它将向 AbstractFactory 传递形状信息 ShapeCIRCLE / RECTANGLE / SQUARE),以便获取它所需对象的类型。同时它还向 AbstractFactory 传递颜色信息 ColorRED / GREEN / BLUE),以便获取它所需对象的类型。

技术图片

Shape

public interface Shape{
   void draw();
}

Circle

public class Circle implements Shape{
   @Override
   public void draw(){
       System.out.println("this is circle");
  }
}

Rectangle

public class Rectangle{
   
   @Override
   public void draw(){
       System.out.println("this is rectangle");
  }
}

Color

public interface Color{
   void fill();
}

Blue

public class Blue implements Shape{
   @Override
   public void fill() {
       System.out.println("this is blue");
  }
}

Red

public class Red{
   @Override
   public void fill() {
       System.out.println("this is red");
  }
}

AbstractFactory

public interface AbstractFactory{
   Shape getShape(String shapeType);
   Color getColor(String colorType);
}

ShapeFactory

public class ShapeFactory implements AbstractFactory {
   @Override
   public Shape getShape(String shapeType) {
       if (shapeType==null || shapeType.equals("")){
           return null;
      }
       if (shapeType.equals("CIRCLE")){
           return new Circle();
      }else if (shapeType.equals("RECTANGLE")){
           return new Rectangle();
      }else if (shapeType.equals("SQUARE")){
           return new Square();
      }
       return null;
  }

   @Override
   public Color getColor(String colorType) {
       return null;
  }
}

ColorFactory

public class ColorFactory implements AbstractFactory {

   @Override
   public Shape getShape(String shapeType) {
       return null;
  }

   @Override
   public Color getColor(String colorType) {

       if (colorType==null || colorType.equals("")){
           return null;
      }
       if (colorType.equals("RED")){
           return new Red();
      }else if (colorType.equals("BLUE")){
           return new Blue();
      }else if (colorType.equals("GREEN")){
           return new Green();
      }
       return null;
  }
}

FactoryProducer

public class FactoryProducer{
   private AbstractFactory factory;
   
   public  AbstractFactory getFactory(String factoryType){
       
        if (factoryType==null || factoryType.equals("")){
           return null;
      }
       if (factoryName.equals("SHAPE")){
           factory =new ShapeFactory();
      }else if (factoryName.equals("COLOR")){
         factory = new ColorFactory();
      }
       return factory;
  }
}

Test

public class Test {

   public static void main(String[] args) {
       FactoryProducer factoryProducer=new FactoryProducer();
       AbstractFactory shapeFactory =factoryProducer.getFactory("SHAPE");
       AbstractFactory colorFactory=factoryProducer.getFactory("COLOR");
       Shape shape=shapeFactory.getShape("CIRCLE");
       Color color=colorFactory.getColor("RED");
       shape.draw();
       color.fill();
  }
}

控制台输出:

drawing circle
this is red

4. 装饰器模式

装饰器模式允许向一个现有的对象添加新的功能,同时又不改变其结构。它是作为现有的类的一个包装,

这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。在某些情况下可替代继承。

意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。

主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。

何时使用:在不想增加很多子类的情况下扩展类。

如何解决:将具体功能职责划分,同时继承装饰者模式。

关键代码: 1、Component 类充当抽象角色,不应该具体实现。 2、修饰类引用和继承 Component 类,具体扩展类重写父类方法。

实现:

创建一个 Shape 接口和实现了 Shape 接口的实体类。然后我们创建一个实现了 Shape 接口的抽象装饰类 ShapeDecorator,并把 Shape 对象作为它的实例变量。RedShapeDecorator 是实现了 ShapeDecorator 的实体类。

技术图片

Shape

public interface Shape{
   void draw();
}

Circle

public class Circle implements Shape{
   @Override
   public void draw(){
       System.out.println("this is circle");
  }
}

Rectangle

public class Rectangle{
   
   @Override
   public void draw(){
       System.out.println("this is rectangle");
  }
}

ShapeDecorator

public abstract class ShapeDecorator implements Shape{
   private Shape decoratorShape;
   
   public ShapeDecorator(Shape decoratorShape){
       this.decoratorShape=decoratorShape;
  }
   
   @Override
   public void draw(){
       decoratorShape.draw();
  }
}

RedShapeDecorator

public class RedShapeDecorator extends ShapeDecorator{
   public RedShapeDecorator(Shape decoratorShape) {
       super(decoratorShape);
  }
   
    @Override
   public void draw() {
       decoratorShape.draw();
       setRedDecorator();
  }


   public void setRedDecorator(){
       System.out.println("border color : red");
  }
}

Test

public class Test {

   public static void main(String[] args) {
       Shape circle =new Circle();
       Shape redCircle =new RedShapeDecorator(new Circle());
       Shape redRectangle =new RedShapeDecorator(new Rectangle());

       circle.draw();
       System.out.println();
       redCircle.draw();
       System.out.println();
       redRectangle.draw();
  }
}

控制台输出:

draw circle

draw circle
border color : red

draw rectangle
border color : red

5. 适配者模式

适配器模式是作为两个不兼容的接口之间的桥梁。它结合了两个独立接口的功能。这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。例如,某个音频播放器只能播放mp3文件,通过一个更高级的音频播放器来播放mp4和vlc。

意图:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

主要解决:主要解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的。

何时使用: 1、系统需要使用现有的类,而此类的接口不符合系统的需要。 2、想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。 3、通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口。)

如何解决:继承或依赖(推荐)。

关键代码:适配器继承或依赖已有的对象,实现想要的目标接口

实现:

我们有一个 MediaPlayer 接口和一个实现了 MediaPlayer 接口的实体类 AudioPlayer。默认情况下,AudioPlayer 可以播放 mp3 格式的音频文件。我们还有另一个接口 AdvancedMediaPlayer 和实现了 AdvancedMediaPlayer 接口的实体类。该类可以播放 vlc 和 mp4 格式的文件。我们想要让 AudioPlayer 播放其他格式的音频文件。为了实现这个功能,我们需要创建一个实现了 MediaPlayer 接口的适配器类 MediaAdapter,并使用 AdvancedMediaPlayer 对象来播放所需的格式。AudioPlayer 使用适配器类 MediaAdapter 传递所需的音频类型,而不需要知道能播放所需格式音频的实际类。

技术图片

MediaPlayer

public interface MediaPlayer {
   void play(String audioType, String fileName);
}

AdvancedMediaPlayer

public interface AdvancedMediaPlayer {

   void playVLC(String fileName);

   void playMP4(String fileName);
}

VLCPlayer

public class VLCPlayer implements AdvancedMediaPlayer {

   @Override
   public void playVLC(String fileName) {
       System.out.println("vlc player: "+fileName);
  }

   @Override
   public void playMP4(String fileName) {

  }
}

MP4Player

public class MP4Player implements AdvancedMediaPlayer {

   @Override
   public void playVLC(String fileName) {

  }

   @Override
   public void playMP4(String fileName) {
       System.out.println("mp4 player: "+fileName);
  }
}

MediaAdapter

public class MediaAdapter implements MediaPlayer {

   private AdvancedMediaPlayer advancedMediaPlayer;

   public MediaAdapter(String audioType) {
     if (audioType.equalsIgnoreCase("vlc")){
         advancedMediaPlayer = new VLCPlayer();
    }else if (audioType.equalsIgnoreCase("mp4")){
         advancedMediaPlayer = new MP4Player();
    }
  }

   @Override
   public void play(String audioType, String fileName) {
       if (audioType.equalsIgnoreCase("vlc")){
           advancedMediaPlayer.playVLC(fileName);
      }else if (audioType.equalsIgnoreCase("mp4")){
           advancedMediaPlayer.playMP4(fileName);
      }
  }
}

AudioPlayer

public class AudioPlayer implements MediaPlayer {

   private MediaAdapter mediaAdapter;

   @Override
   public void play(String audioType, String fileName) {
       //播放mp3音乐的内置支持
       if (audioType.equals("mp3")){
           System.out.println("playing mp3: "+fileName);
      }else if (audioType.equals("vlc")||audioType.equals("mp4")){
           mediaAdapter=new MediaAdapter(audioType);
           mediaAdapter.play(audioType, fileName);
      }
  }
}

Test

public class Test {

   public static void main(String[] args) {
       AudioPlayer player = new AudioPlayer();
       player.play("mp3", "test.mp3");
       player.play("mp4", "test.mp4");
       player.play("vlc", "test.vlc");
  }
}

控制台输出:

playing mp3: test.mp3
mp4 player: test.mp4
vlc player: test.vlc

6. 代理模式

在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。

意图:为其他对象提供一种代理以控制对这个对象的访问。

主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。

何时使用:想在访问一个类时做一些控制。

如何解决:增加中间层。

关键代码:实现与被代理类组合。

实现:

创建一个BuyMAC接口及实现该接口的实体类,ProxyBuyMAC是一个代理类,减少对Person对象加载的内存占用。

技术图片

BuyMAC

public interface BuyMAC {
   void buyMAC();
}

Person

public class Person implements BuyMAC {
   private String name;
   
   public Person(String name) {
       System.out.println("this is person constructor");
       this.name = name;
  }
   
   @Override
   public void buyMAC() {
       System.out.println(this.name+" want to buy a MAC");
  }
   
     public String getName() {
       return name;
  }

   public void setName(String name) {
       this.name = name;
  }
}

ProxyBuyMAC

public class ProxyBuyMAC implements BuyMAC {


   private Person person;

   private String name;

   public ProxyBuyMAC(String name) {
       System.out.println("this is proxyMAC constructor");
       this.name = name;
  }

   @Override
   public void buyMAC() {
       if (person==null){
           person=new Person(this.name);
      }
       person.buyMAC();
  }
}

Test

public class Test {


   public static void main(String[] args) {
       BuyMAC buyMAC=new ProxyBuyMAC("Reece");
       buyMAC.buyMAC();
  }
}

控制台输出:

this is proxyMAC constructor
this is person constructor
Reece want to buy a MAC from Amazon

注:1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。 2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。

7. 观察者模式

当对象间存在一对多关系时,则使用观察者模式。比如,当一个对象被修改时,则会自动通知它的依赖对象。

意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。

何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。

如何解决:使用面向对象技术,可以将这种依赖关系弱化。

关键代码:在抽象类里有一个 ArrayList 存放观察者们。

实现:

观察者模式使用三个类 Subject、Observer 和 Client。Subject 对象带有绑定观察者到 Client 对象和从 Client 对象解绑观察者的方法。我们创建 Subject 类、Observer 抽象类和扩展了抽象类 Observer 的实体类。

技术图片

Subject

public class Subject {

   private List<Observer> observers;

   private int state;

   public int getState() {
       return state;
  }

   public void setState(int state) {
       this.state = state;
       notifyAllObservers();
  }


   public void attach(Observer observer){
       if (observers==null){
           observers=new ArrayList<>();
      }
       observers.add(observer);
  }

   public void notifyAllObservers(){
       for (Observer observer:observers){
           observer.update();
      }
  }
}

Observer

public abstract class Observer {

   public Subject subject;//被观察的对象

   public abstract void update();
}

HexaObserver

public class HexaObserver extends Observer {

   public Subject subject;

   public HexaObserver(Subject subject) {
       this.subject = subject;
       this.subject.attach(this);
  }

   @Override
   public void update() {
       System.out.println("hexa String: "+Integer.toHexString(subject.getState()).toUpperCase());
  }
}

BinaryObserver

public class BinaryObserver extends Observer {

   public Subject subject;


   public BinaryObserver(Subject subject) {
       this.subject = subject;
       this.subject.attach(this);
  }

   @Override
   public void update() {
       System.out.println("Binary String: "+Integer.toBinaryString(subject.getState()));
  }
}

OctalObserver

public class OctalObserver extends Observer {

   public Subject subject;


   public OctalObserver(Subject subject) {
       this.subject = subject;
       this.subject.attach(this);
  }

   @Override
   public void update() {
       System.out.println("Octal String: "+Integer.toOctalString(subject.getState()));
  }
}

Test

public class Test {

   public static void main(String[] args) {
       Subject subject=new Subject();
       Observer binary=new BinaryObserver(subject);
       Observer Octal =new OctalObserver(subject);
       Observer hexa=new HexaObserver(subject);

       System.out.println("First state change: 15");
       subject.setState(15);
       System.out.println("Second state change: 20");
       subject.setState(20);
  }
}

控制台输出:

First state change: 15
Binary String: 1111
Octal String: 17
hexa String: F
Second state change: 20
Binary String: 10100
Octal String: 24
hexa String: 14

8. 命令模式

 命令模式是一种数据驱动的设计模式,请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。

意图:将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。

主要解决:在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。

何时使用:在某些场合,比如要对行为进行"记录、撤销/重做、事务"等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将"行为请求者"与"行为实现者"解耦?将一组行为抽象为对象,可以实现二者之间的松耦合。

如何解决:通过调用者调用接受者执行命令,顺序:调用者→接受者→命令。

关键代码:定义三个角色:1、received 真正的命令执行对象 2、Command 3、invoker 使用命令对象的入口

实现:

首先创建作为命令的接口 Order,然后创建作为请求的 Stock 类。实体命令类 BuyStock 和 SellStock,实现了 Order 接口,将执行实际的命令处理。创建作为调用对象的类 Broker,它接受订单并能下订单。Broker 对象使用命令模式,基于命令的类型确定哪个对象执行哪个命令。

技术图片

Order

public interface Order {
   void execute();
}

Stock

public class Stock {

   private String name;

   private int quantity;

   public Stock(String name, int quantity) {
       this.name = name;
       this.quantity = quantity;
  }

   public void buy(){
       System.out.println(quantity+" "+name+" were bought");
  }

   public void sell(){
       System.out.println(quantity+" "+name+" were sold");
  }
}

SellStock

public class SellStock implements Order {

   private Stock stock;

   public SellStock(Stock stock) {
       this.stock = stock;
  }

   @Override
   public void execute() {
       stock.sell();
  }
}

BuyStock

public class BuyStock implements Order {

   private Stock stock;


   public BuyStock(Stock stock) {
       this.stock = stock;
  }

   @Override
   public void execute() {
       stock.buy();
  }

}

Broker

public class Broker {

   private List<Order> orders;

   public void takeOrder(Order order){
       if (orders==null){
           orders = new ArrayList<Order>();
      }
       orders.add(order);
  }

   public void executeOrder(){
      for(Order order :orders){
          order.execute();
      }
      orders.clear();
  }
}

Test

public class Test {
   public static void main(String[] args) {
       Stock stock=new Stock("apple",10);

       Order buyStock=new BuyStock(stock);
       Order sellStock=new SellStock(stock);

       Broker broker=new Broker();

       broker.takeOrder(buyStock);
       broker.takeOrder(sellStock);

       broker.executeOrder();
  }
}

控制台输出:

10 apple were bought
10 apple were sold

以上是关于几种常见设计模式的总结的主要内容,如果未能解决你的问题,请参考以下文章

java常见的几种设计模式

hadoop的mapreduce常见算法案例有几种

常见的8种设计模式

常见 Web 安全攻防总结

常见web安全攻防总结

总结几种常见web攻击手段及其防御方式