디자인 패턴
- 재사용 가능한 객체 지향 소프트웨어의 기본
디자인 패턴은 대부분의 사람들, 분류 된 카탈로그 및 코드 디자인 경험으로 알려진 반복적 인 사용 세트입니다. 디자인 패턴을 사용하는 것은 코드를 재사용하고, 다른 사람들이 코드를 쉽게 이해할 수 있도록하며, 코드의 신뢰성을 보장하는 것입니다. 디자인 패턴이 그 자체, 다른 사람 및 시스템을위한 윈윈이라는 것은 의심의 여지가 없습니다. 설계 패턴은 코드 컴파일을 진정으로 엔지니어링합니다. 디자인 패턴은 건물의 벽돌과 돌처럼 소프트웨어 엔지니어링의 초석입니다. 프로젝트에서 디자인 패턴을 합리적으로 사용하면 많은 문제를 완벽하게 해결할 수 있습니다. 각 패턴은 이제 해당 원리에 해당합니다. 각 패턴은 우리 주변에서 끊임없이 반복되는 문제와 문제의 핵심 솔루션을 설명하며, 이것이 널리 사용될 수있는 이유이기도합니다. 이 장은 Java의 아름다움의 디자인 모델 [신인부터 전문가로 진화합니다] 시리즈입니다. 우리는이 장을 이론과 실천의 조합으로 연구 할 것입니다. 프로그램 애호가가 디자인 모델을 잘 배우고 우수한 소프트웨어 엔지니어가되기를 바랍니다.
1. 디자인 패턴의 분류
전반적으로 설계 패턴은 세 가지 범주로 나뉩니다.
공장 방법 모드, 추상 공장 모드, 싱글 톤 모드, 빌더 모드 및 프로토 타입 모드의 5 가지 유형의 생성 모드가 있습니다.
어댑터 모드, 데코레이터 모드, 프록시 모드, 모양 모드, 브리지 모드, 조합 모드 및 즐거움 모드의 7 가지 구조 모드가 있습니다.
동작 모드, 총 11 개의 : 정책 모드, 템플릿 메소드 모드, 관찰자 모드, 반복 하위 모드, 책임 체인 모드, 명령 모드, 메모 모드, 상태 모드, 방문자 모드, 중재자 모드 및 통역 모드.
실제로 동시 모드와 스레드 풀 모드의 다른 두 가지 범주가 있습니다. 사진을 사용하여 전체적으로 설명합시다.
2. 디자인 모델의 6 가지 원칙
1. 가까운 원리를 열어줍니다
개방 및 폐쇄의 원리는 확장 및 수정에 가깝게 개방하는 것입니다. 프로그램을 확장 해야하는 경우 원래 코드를 수정하여 핫 플러그 효과를 얻을 수 없습니다. 한마디로, 그것은 프로그램을보다 확장 가능하고 유지 관리 및 업그레이드하기 쉽게하기 위해서입니다. 이러한 효과를 달성하려면 인터페이스와 추상 클래스를 사용해야합니다.이 클래스는 나중에 특정 디자인에서 언급 할 것입니다.
2. Liskov 대체 원리
Liskov 대체 원리 LSP는 객체 지향 설계의 기본 원칙 중 하나입니다. 풍부한 대체 원칙에 따르면 모든 기본 클래스가 나타날 수 있으며 서브 클래스는 확실히 나타날 수 있습니다. LSP는 상속 및 재사용의 초석입니다. 미분 클래스가 기본 클래스를 대체 할 수 있고 소프트웨어 장치의 함수가 영향을받지 않는 경우에만 기본 클래스를 실제로 재사용 할 수 있으며, 파생 클래스는 기본 클래스를 기반으로 새로운 동작을 추가 할 수 있습니다. Richter 대체 원칙은 "오픈-래킹"원칙에 대한 보충입니다. "개방형"원칙을 구현하기위한 핵심 단계는 추상화입니다. 기본 클래스와 서브 클래스 사이의 상속 관계는 추상화의 구체적인 구현이므로, 풍부한 대체 원칙은 추상화를 구현하기위한 특정 단계의 표준화입니다. - 바이두 백과 사전에서
3. 의존성 반전 원리
이것이 개방 및 폐쇄의 원리의 기초입니다. 특정 내용 : 인터페이스의 실제 프로그래밍은 콘크리트보다는 추상화에 따라 다릅니다.
4. 인터페이스 분리 원리
이 원칙은 다음을 의미합니다. 여러 분리 된 인터페이스를 사용하는 것이 단일 인터페이스를 사용하는 것보다 낫습니다. 또한 클래스 사이의 커플 링 정도를 줄이는 것을 의미합니다. 여기에서 설계 패턴은 실제로 업그레이드 및 유지 관리의 편의를 위해 대형 소프트웨어 아키텍처에서 시작하여 소프트웨어의 디자인 아이디어임을 알 수 있습니다. 따라서 위의 기사는 여러 번 나타났습니다. 의존성을 줄이고 커플 링을 줄입니다.
5. Demeter 원리
지식이 가장 적은 이유는 무엇입니까? 즉, 한 엔티티는 시스템 기능 모듈이 비교적 독립적이되도록 가능한 한 적은 다른 엔터티와 상호 작용해야합니다.
6. 복합 재사용 원리
원칙은 상속이 아닌 합성/응집 방법을 사용하는 것입니다.
3. Java의 23 개의 디자인 패턴
이 섹션에서 시작하여 Java의 23 개 설계 패턴의 개념, 응용 프로그램 시나리오 등을 자세히 소개하고 특성 및 설계 패턴의 원리와 함께 분석합니다.
1. 공장 방법
공장 방법 모드에는 세 가지 유형이 있습니다.
11. 일반 공장 모델은 공장 클래스를 설정하고 동일한 인터페이스를 구현하는 일부 클래스의 인스턴스를 만드는 것입니다. 먼저 관계 다이어그램을보십시오.
예를 들면 다음과 같습니다. (이메일 및 문자 메시지를 보내는 예를 들어 봅시다)
먼저 둘 사이의 공통 인터페이스를 만듭니다.
공개 인터페이스 발신자 {public void send (); } 둘째, 구현 클래스를 만듭니다.
Public Class Mailsender는 발신자를 구현합니다 {@override public void send () {System.out.println ( "이것은 MailSender!"); }} public class smssender 구현 발신자 {@override public void send () {System.out.println ( "이것은 SMS Sender!"); }}마지막으로 공장 건설 :
공개 클래스 sendFactory {public sender produce (문자열 유형) {if ( "mail".Equals (type)) {return new MailSender (); } else if ( "sms".equals (type)) {return new smssender (); } else {System.out.println ( "올바른 유형을 입력하십시오!"); 널 리턴; }}}테스트하자 :
공개 클래스 팩토리 테스트 {public static void main (String [] args) {sendFactory factory = new sendFactory (); 발신자 sender = factory.proctor ( "SMS"); sender.send (); }}출력 : 이것은 SMS 발신자입니다!
22. 다중 공장 방법 모드는 일반 공장 방법 모드를 개선합니다. 일반 팩토리 메소드 모드에서 전달 된 문자열이 잘못된 경우 객체를 올바르게 만들 수 없습니다. 다중 공장 방법 모드는 개별적으로 개체를 만들기위한 여러 공장 방법을 제공합니다. 관계 다이어그램 :
위의 코드를 수정하고 다음과 같이 SendFactory 클래스를 변경하십시오.
공개 클래스 sendFactory {public sender produceMail () {return new MailSender (); } public sender producessms () {return new smssender (); }} 테스트 클래스는 다음과 같습니다.
공개 클래스 팩토리 테스트 {public static void main (String [] args) {sendFactory factory = new sendFactory (); 발신자 sender = factory.producemail (); sender.send (); }}출력 : 이것은 MailSender입니다!
33. 정적 공장 방법 모드 , 위의 여러 공장 메소드 모드의 메소드를 정적으로 설정하고 인스턴스를 만들 필요가없고 직접 호출 할 필요가 없습니다.
공개 클래스 sendFactory {public static 발신자 produceMail () {return new MailSender (); } public static 발신자 producesms () {return new smssender (); }} Public Class FactoryTest {public static void main (String [] args) {sender sender = sendFactory.produceMail (); sender.send (); }}출력 : 이것은 MailSender입니다!
전반적으로, 공장 모델은 적합합니다. 많은 제품을 생성해야하고 공통 인터페이스를 가질 때 공장 방법 모델을 통해 만들 수 있습니다. 위의 세 가지 모드 중 첫 번째 모드는 통과 된 문자열이 잘못된 경우 객체를 올바르게 만들 수 없으며 세 번째 모드는 두 번째 클래스에 비해 공장 클래스를 인스턴스화 할 필요가 없습니다. 따라서 대부분의 경우 정적 공장 메소드 모드 인 세 번째를 선택합니다.
2. 초록 공장 패턴
공장 방법 모델에는 문제가 있는데, 이는 클래스의 생성이 공장 수업에 달려 있다는 것입니다. 즉, 프로그램을 확장하려면 폐쇄 원칙을 위반하는 공장 클래스를 수정해야합니다. 따라서 설계 관점에서 특정 문제가 있습니다. 그것을 해결하는 방법? 이것은 추상 공장 패턴을 사용하여 여러 공장 클래스를 만듭니다. 이런 식으로 새로운 기능이 필요한 후에는 이전 코드를 수정하지 않고 새로운 공장 클래스를 직접 추가 할 수 있습니다. 추상 공장은 이해하기 쉽지 않기 때문에 먼저 다이어그램을보고 코드를 따라 이해하기 쉽습니다.
예를 참조하십시오 :
공개 인터페이스 발신자 {public void send (); }두 가지 구현 클래스 :
Public Class Mailsender는 발신자를 구현합니다 {@override public void send () {System.out.println ( "이것은 MailSender!"); }} public class smssender 구현 발신자 {@override public void send () {System.out.println ( "이것은 SMS Sender!"); }}두 가지 공장 카테고리 :
공개 클래스 sendmailfactory emplesments 제공자 {@override public sender product () {return new MailSender (); }} 공개 클래스 sendsmsfactory emplements 제공자 {@override public sender product () {return new smssender (); }} 인터페이스 제공 :
공개 인터페이스 제공자 {public sender produce (); }테스트 클래스 :
공개 클래스 테스트 {public static void main (String [] args) {제공자 제공자 = new sendMailFactory (); 발신자 sender = provider.proctord (); sender.send (); }}실제로이 모델의 장점은 현재 함수를 추가하려면 다음과 같은 경우, 적시 정보를 보내려면 구현 클래스를 만들고, 발신자 인터페이스를 구현하고, 동시에 공장 클래스를 만들고, 공급자 인터페이스를 구현하고, 이는 정상이며, 기성품 코드를 변경할 필요가 없다는 것입니다. 이렇게하면 더욱 확장 가능합니다!
3. 싱글 톤 모드
싱글 톤은 일반적으로 사용되는 디자인 패턴입니다. Java 응용 프로그램에서 싱글 톤 객체는 JVM에서 객체의 인스턴스 만 있으면 보장 할 수 있습니다. 이 모델에는 몇 가지 이점이 있습니다.
1. 일부 클래스는 더 자주 생성되며 일부 큰 개체의 경우 이는 엄청난 시스템 오버 헤드입니다.
2. 새 연산자가 제거되고 시스템 메모리의 빈도가 줄어들고 GC의 압력이 줄어 듭니다.
3. 거래소의 핵심 거래 엔진과 같은 일부 범주는 거래 프로세스를 제어합니다. 여러 범주를 만들 수 있다면 시스템이 완전히 엉망이됩니다. (예를 들어, 여러 사령관이 군대 사령부에 동시에 나타나는 경우 확실히 혼란에 빠질 것입니다) 따라서 싱글 톤 모델을 사용하면 핵심 트랜잭션 서버가 전체 프로세스를 독립적으로 제어 할 수 있습니다.
먼저 간단한 싱글 톤 수업을 작성해 봅시다.
공개 클래스 싱글 톤 { /* 참조를 방지하기 위해 개인 정적 인스턴스를 보유합니다. 이 값은 게으른 하중을 달성하기 위해 널입니다.*/ private static singleton instance = null; /* 인스턴스화를 방지하기위한 개인 생성자*/ private singleton () {}/* 인스턴스를 만들기위한 정적 엔지니어링 메소드*/ public static singleton getInstance () {if (instance == null) {instance = new Singleton (); } return 인스턴스; } /* 객체가 직렬화에 사용되는 경우 직렬화 전후에 객체가 일관성을 유지하도록 할 수 있습니다.* / public 객체 readResolve () {return instance; }}이 클래스는 기본 요구 사항을 충족 할 수 있지만,이 클래스는 다중 스레드 환경에서 무선 무선 보안 보호 기능을 사용하면 확실히 문제가있을 것입니다. 그것을 해결하는 방법? 먼저 GetInstance 메소드에 동기화 된 키워드를 추가 할 것이라고 생각합니다.
public static synchronized singleton getinstance () {if (instance == null) {instance = new Singleton (); } return 인스턴스; }그러나 동기화 된 키워드는이 객체를 잠그고 있습니다. getInstance () 호출 할 때마다 객체를 잠겨 있어야하기 때문에이 사용법은 성능이 감소합니다. 실제로, 객체가 처음으로 생성 될 때만 잠글 필요는 없으므로이 장소를 개선해야합니다. 다음으로 변경합시다.
public static singleton getinstance () {if (instance == null) {synchronized (instance) {if (instance == null) {instance = new Singleton (); }}} return 인스턴스; }앞에서 언급 한 문제를 해결하고 내부에 동기화 된 키워드를 추가하는 것 같습니다. 즉, 호출 할 때 잠금 할 필요가 없으며 인스턴스가 NULL이고 객체가 생성 될 때만 잠금 만 필요합니다. 그러나이 경우 문제가있을 수 있습니다. 다음 상황을 살펴보십시오. Java 지침에서 개체 및 할당 작업 생성, 즉 인스턴스 = New Singleton (); 진술은 두 단계로 실행됩니다. 그러나 JVM은이 두 작업의 순서를 보장하지 않으므로 JVM이 새로운 싱글 톤 인스턴스에 공간을 할당 한 다음 인스턴스 멤버에 직접 할당 한 다음 싱글 톤 인스턴스를 초기화 할 수 있습니다. 이것은 오류가 발생할 수 있습니다. A와 B 스레드를 예로 들어 봅시다.
a> a 및 b 스레드는 동시에 판단 IF 판결을 입력합니다.
b> a 첫 번째는 동기화 된 블록으로 들어갑니다. 인스턴스는 null이므로 instance = new Singleton ();
C> JVM 내부의 최적화 메커니즘으로 인해 JVM은 먼저 싱글 톤 인스턴스에 할당 된 빈 메모리를 그려 인스턴스 멤버에 할당합니다 (이 시간에 JVM 이이 인스턴스를 초기화하지 않음).
d> b는 동기화 된 블록으로 들어갑니다. 현재 인스턴스가 NULL이 아니기 때문에 즉시 동기화 된 블록을 남기고 결과를 메소드라고하는 프로그램에 반환합니다.
e> 현재, Thread B는 싱글 톤 인스턴스를 사용하려고하지만 초기화되지 않았으므로 오류가 발생합니다.
따라서 프로그램에는 여전히 가능한 오류가 있습니다. 실제로, 프로그램의 실행 프로세스는 매우 복잡합니다. 이 시점에서 우리는 특히 다중 스레드 환경에서 프로그램을 작성하는 것이 더 어렵고 도전적이라는 것을 알 수 있습니다. 우리는 프로그램을 추가로 최적화했습니다.
개인 정적 클래스 SingletOnfactory {private static singleton instance = new Singleton (); } public static singleton getinstance () {return singletonfactory.instance; }실제 상황은 싱글 톤 패턴이 내부 클래스를 사용하여 싱글 톤의 구현을 유지한다는 것입니다. JVM의 내부 메커니즘은 클래스가로드되면이 클래스의 로딩 프로세스가 스레드에 상호 배타적이지 않도록 보장 할 수 있습니다. 이런 식으로, 우리가 처음으로 GetInstance를 호출 할 때, JVM은 인스턴스가 한 번만 생성되도록하고 인스턴스에 할당 된 메모리가 초기화되도록하는 데 도움이되므로 위의 문제에 대해 걱정할 필요가 없습니다. 동시에,이 방법은 처음으로 요구 될 때만 상호 배제 메커니즘 만 사용하여 성능 저하의 문제를 해결합니다. 이런 식으로, 우리는 완벽한 싱글 톤 패턴을 일시적으로 요약합니다.
공개 클래스 싱글 톤 { /* 인스턴스화를 방지하기위한 개인 생성자 방법* / private singleton () {} /* 여기에서 내부 클래스를 사용하여 싱글 톤* / private static singletonfactory {private static singleton instance = new Singleton (); } /* 인스턴스를 가져옵니다* / public static singleton getInstance () {return singletonfactory.instance; } /* 객체가 직렬화에 사용되는 경우 직렬화 전후에 객체가 일관성을 유지하도록 할 수 있습니다.* / public 객체 readResolve () {return getInstance (); }}사실, 그것이 완벽하다는 것이 반드시 사실은 아닙니다. 생성자에 예외가 발생하면 인스턴스가 생성되지 않으며 오류가 발생합니다. 따라서 완벽한 것은 없습니다. 실제 상황에 따라 응용 프로그램 시나리오에 가장 적합한 구현 방법 만 선택할 수 있습니다. 어떤 사람들은 또한 이것을 구현합니다. 우리는 생성과 getInstance ()를 분리하고 생성에 동기화 된 키워드를 별도로 추가하는 한 클래스를 생성 할 때만 동기화하면됩니다. 가능합니다.
공개 클래스 SingleTontest {private static singletontest instance = null; private singletontest () {} private static synchronized void syncinit () {if (instance == null) {instance = new SingletOntest (); }} public static singletontest getInstance () {if (instance == null) {syncinit (); } return 인스턴스; }}성능을 고려하면 전체 프로그램은 인스턴스 만 한 번만 만들어야하므로 성능에 영향을 미치지 않습니다.
보충 : "섀도우 인스턴스"메소드는 싱글 톤 객체의 특성을 동기화하는 데 사용됩니다.
공개 클래스 SingleTontest {private static singletontest instance = null; 개인 벡터 속성 = null; 공개 벡터 getProperties () {return 속성; } private singletontest () {} private static synchronized void syncinit () {if (instance == null) {instance = new SingletOntest (); }} public static singletontest getInstance () {if (instance == null) {syncinit (); } return 인스턴스; } public void updateProperties () {SingletOntest Shadow = new SingletOntest (); 속성 = 섀도우.getProperties (); }}싱글 톤 패턴을 학습함으로써 우리는 다음과 같이 말합니다.
1. 싱글 톤 모델을 이해하는 것은 간단하지만 자세히 구현하기는 여전히 어렵습니다.
2. 동기화 된 키워드는 객체를 잠그는 것입니다. 그것을 사용할 때는 적절한 장소에서 사용해야합니다 (잠금 해야하는 객체와 프로세스를 잠그고 때로는 전체 객체와 전체 프로세스가 잠겨 필요하지 않음).
이 시점에서 싱글 톤 패턴은 기본적으로 이야기되었습니다. 결국, 저자는 갑자기 다른 질문을 생각했습니다.이 질문은 정적 클래스 방법을 사용하여 싱글 톤 패턴의 효과를 달성하는 것입니다. 여기서 둘의 차이점은 무엇입니까?
첫째, 정적 클래스는 인터페이스를 구현할 수 없습니다. (클래스의 관점에서 괜찮지 만 정적을 파괴 할 것입니다. 인터페이스에 정적 수정 방법이 없기 때문에 구현하더라도 정적이 아닌 것입니다).
둘째, 싱글 톤은 초기화 지연 될 수 있으며, 처음으로로드 할 때 정적 클래스가 초기화됩니다. 게으른 하중의 이유는 일부 클래스가 비교적 크기 때문에 게으른 하중이 성능을 향상시키는 데 도움이되기 때문입니다.
다시 말하지만, 싱글 톤 클래스를 상속받을 수 있고 그 방법을 덮어 쓸 수 있습니다. 그러나 정적 클래스의 내부 방법은 정적이며 덮어 쓸 수 없습니다.
마지막으로, 싱글 톤 수업은 더 유연합니다. 결국, 그들은 구현 측면에서 일반적인 Java 클래스 일뿐입니다. 싱글 톤의 기본 요구를 충족하는 한 원하는대로 다른 기능을 구현할 수 있지만 정적 클래스는 할 수 없습니다. 위의 요약에서 기본적으로 둘 사이의 차이점을 볼 수 있습니다. 반면에, 우리가 위에서 마지막으로 구현 한 싱글 톤 패턴은 정적 클래스와 내부적으로 구현되므로 두 가지는 매우 관련이 있지만 문제에 대한 우리의 고려 수준은 다릅니다. 두 아이디어를 결합 하여만 완벽한 솔루션을 만들 수 있습니다. Hashmap이 배열 + 링크 된 목록을 사용하여 구현하는 것처럼 실제로 인생의 많은 것들이 다음과 같습니다. 문제를 다루기 위해 다른 방법을 사용하면 항상 장점과 단점이 있습니다. 가장 완벽한 방법은 각 방법의 장점을 결합하여 문제를 가장 잘 해결하는 것입니다!
4. 빌더 모드
공장 클래스 모델은 단일 클래스를 만드는 패턴을 제공하는 반면, 빌더 모델은 관리를 위해 다양한 제품을 집중시키고이를 사용하여 복합 객체를 생성합니다. 소위 복합 객체는 다른 속성을 가진 특정 클래스를 나타냅니다. 실제로, 빌더 모델은 이전의 추상 공장 모델과 최종 테스트를 결합하여 얻습니다. 코드를 살펴 보겠습니다.
또한 이전과 마찬가지로 1 개의 발신자 인터페이스와 2 개의 구현 클래스 MailSender 및 SMSSender. 마지막으로, 빌더 클래스는 다음과 같습니다.
공개 클래스 빌더 {private list <Sender> list = new ArrayList <Sender> (); public void producemailsender (int count) {for (int i = 0; i <count; i ++) {list.add (new MailSender ()); }} public void producessmssender (int count) {for (int i = 0; i <count; i ++) {list.add (new smssender ()); }}}테스트 클래스 :
공개 클래스 테스트 {public static void main (String [] args) {builder builder = new Builder (); Builder.ProduceMailsender (10); }}이 관점에서 빌더 패턴은 많은 기능을 클래스에 통합하여 더 복잡한 것들을 만들 수 있습니다. 따라서 엔지니어링 모델과의 차이점은 공장 모델이 단일 제품을 만드는 데 중점을 두는 반면, 빌더 모델은 적합한 객체와 여러 부품을 만드는 데 중점을 둡니다. 따라서 공장 모델 또는 빌더 모델을 선택할지 여부는 실제 상황에 따라 다릅니다.
5. 프로토 타입
프로토 타입 패턴은 창의적인 패턴이지만 엔지니어링 패턴과 관련이 없습니다. 이름에서 볼 수 있듯이,이 패턴의 아이디어는 객체를 프로토 타입으로 복사하고 복제하고 원래 객체와 유사한 새로운 객체를 생성하는 것입니다. 이 요약은 객체를 복사하여 설명합니다. Java에서 복사 객체는 Clone ()을 통해 구현되며 프로토 타입 클래스가 먼저 생성됩니다.
공개 클래스 프로토 타입 구현 클로닝 가능 {public Object Clone ()는 ClonenotsupportedException {프로토 타입 프로토 = (프로토 타입) Super.Clone (); 리턴 프로토; }}매우 간단합니다. 프로토 타입 클래스는 복제 가능한 인터페이스 만 구현하고 클론 방법을 덮어 쓸 필요가 있습니다. 클론 가능한 인터페이스가 빈 인터페이스이기 때문에 클론 메소드는 모든 이름으로 변경 될 수 있습니다. 여기서 초점은 문장 Super.Clone ()이기 때문에 Clonea 또는 Cloneb와 같은 구현 클래스의 메소드 이름을 임의로 정의 할 수 있습니다. super.clone ()은 Object Clone () 메소드를 호출하고 객체 클래스에서는 clone ()가 기본입니다. 구체적으로 구현하는 방법? 나는 Java의 지역 방법의 호출을 해석하는 것에 관한 다른 기사에서는 들어 가지 않을 것입니다. 여기서는 얕은 사본과 객체의 깊은 사본을 결합합니다. 우선, 당신은 물체의 깊고 얕은 사본의 개념을 이해해야합니다.
얕은 사본 : 객체를 복사 한 후 기본 데이터 유형의 변수가 재현되고 참조 유형은 원래 객체를 가리 킵니다.
딥 카피 : 객체를 복사 한 후 기본 데이터 유형과 참조 유형이 모두 재현됩니다. 간단히 말해서, 깊은 복사는 완전히 복사되며 얕은 복사는 철저하지 않습니다.
여기에서 복사의 예를 작성하십시오.
공개 클래스 프로토 타입은 클로닝 가능, 직렬화 가능 {개인 정적 최종 긴 SerialversionUID = 1L; 개인 문자열 문자열; 개인 SerializableObject OBJ; /* 얕은 사본*/ public Object Clone ()는 clonenotsupportedException {프로토 타입 프로토 = (프로토 타입) super.clone (); 리턴 프로토; } /* 딥 카피* / public Object DeepClone ()는 ioException, classNotFoundException { /* 이진 스트림을 현재 객체에 쓰기* / bytearRayoutputStream bos = new BytearRayoutputStream (); ObjectOutputStream OOS = 새로운 ObjectOutputStream (BOS); oos.writeobject (this); /* 바이너리 스트림에 의해 생성 된 새 개체를 읽습니다*/ bytearrayinputStream bis = 새로운 BytearRayinputStream (bos.tobytearray ()); ObjectInputStream OIS = New ObjectInputStream (BIS); return ois.readobject (); } public String getString () {return String; } public void setstring (String String) {this.string = String; } public serializableObject getObj () {return obj; } public void setobj (SerializableObject obj) {this.obj = obj; }} Class SerializableObject는 시리얼이즈 가능 {private static final long serialversionuid = 1L; } 딥 카피를 얻으려면 스트림 형태로 현재 객체의 이진 입력을 읽은 다음 이진 데이터에 해당하는 물체를 기록해야합니다.
우리는 디자인 모드에 대해 계속 논의 할 것입니다. 이전 기사에서는 5 개의 창조 모드에 대해 이야기했습니다. 이 장의 시작 부분에서는 어댑터 모드, 장식 모드, 프록시 모드, 모양 모드, 브리지 모드, 조합 모드 및 즐거움 모드의 7 가지 구조 모드에 대해 이야기합니다. 물체의 어댑터 모드는 다양한 모드의 원점입니다. 다음 그림을 살펴 보겠습니다.
어댑터 패턴은 인터페이스 불일치로 인해 클래스의 호환성 문제를 제거하기 위해 클래스의 인터페이스를 클라이언트가 기대하는 다른 인터페이스 표현으로 변환합니다. 주로 어댑터 클래스 모드, 어댑터 객체 모드 및 어댑터 인터페이스 모드의 세 가지 범주로 나뉩니다. 먼저, 클래스의 어댑터 모드를 살펴보고 먼저 클래스 다이어그램을 살펴 보겠습니다.
핵심 아이디어는 : 대상 인터페이스를 대상으로 할 때 적응하고 타겟팅 할 수있는 메소드가있는 소스 클래스가 있습니다. 어댑터 클래스를 통해 소스 함수는 대상으로 확장되고 코드를 읽습니다.
공개 클래스 소스 {public void method1 () {system.out.println ( "이것은 원래 메소드입니다!"); }} 공개 인터페이스 targetable { /* 원래 클래스* / public void method1 ()의 메소드와 동일; /* 새 클래스의 방법*/ public void method2 (); } 공개 클래스 어댑터는 소스 구현을 확장합니다. targetable {@override public void method2 () {system.out.println ( "이것은 대상 방법입니다!"); }}어댑터 클래스는 소스 클래스를 상속하고 대상 인터페이스를 구현합니다. 다음은 테스트 클래스입니다.
public class adaptertest {public static void main (String [] args) {targetable target = new 어댑터 (); 대상 .method1 (); 대상 .method2 (); }}산출:
이것은 원래 방법입니다!
이것이 목표 방법입니다!
이러한 방식으로, Targetable 인터페이스의 구현 클래스에는 소스 클래스의 함수가 있습니다.
물체의 어댑터 모드
기본 아이디어는 클래스의 어댑터 모드와 동일합니다. 어댑터 클래스가 수정됩니다. 이번에는 소스 클래스가 상속되지 않지만 소스 클래스는 호환성 문제를 해결하기 위해 개최됩니다. 사진을보세요 :
어댑터 클래스의 소스 코드 만 수정하십시오.
공개 클래스 래퍼는 대상 {private source source; 공개 래퍼 (소스 소스) {super (); this.source = source; } @override public void method2 () {system.out.println ( "이것은 대상 방법입니다!"); } @override public void method1 () {source.method1 (); }}테스트 클래스 :
public class adaptertest {public static void main (string [] args) {소스 소스 = 새 소스 (); 대상 대상 = 새 포장지 (소스); 대상 .method1 (); 대상 .method2 (); }}출력은 첫 번째 출력과 동일하지만 적응 방법은 다릅니다.
세 번째 어댑터 모드는 인터페이스의 어댑터 모드입니다. 인터페이스의 어댑터는 다음과 같습니다. 때로는 우리가 쓴 인터페이스에 여러 가지 추상 방법이 있습니다. 인터페이스의 구현 클래스를 작성할 때 인터페이스의 모든 메소드를 구현해야합니다. 모든 방법이 필요한 것은 아니며 때로는 일부 만 필요하기 때문에 이것은 분명히 폐기물입니다. 이 문제를 해결하기 위해 인터페이스의 어댑터 모드를 도입했습니다. 추상 클래스의 도움으로 초록 클래스는 인터페이스를 구현하고 모든 방법을 구현합니다. 우리는 원래 인터페이스를 다루지 않으며 추상 클래스와 만 연락합니다. 그래서 우리는 수업을 작성하고, 추상 클래스를 물려 받고, 필요한 방법을 다시 작성합니다. 클래스 다이어그램을 살펴보십시오.
이해하기 쉽습니다. 실제 개발에서, 우리는 종종이 인터페이스에 정의 된 너무 많은 방법을 만나기 때문에 때로는 일부 구현 클래스에서 필요하지 않습니다. 코드보기 :
공용 인터페이스 Sourcable {public void method1 (); 공개 무효 방법 2 (); }추상 클래스 래퍼 2 :
공개 초록 클래스 Wrapper2를 구현할 수있는 {public void method1 () {} public void method2 () {}} public class sourcesub1은 wrapper2 {public void method1 () {system.out.println ( "Sourcable Interface의 첫 번째 sub1!")을 확장합니다. }} public class sourcesub2는 wrapper2 {public void method2 () {system.out.println ( "Sourcable Interface의 두 번째 sub2!"); }} public class wrappertest {public static void main (string [] args) {sourcable source1 = new sourcesub1 (); sourcable source2 = 새로운 sourcesub2 (); source1.method1 (); source1.method2 (); source2.method1 (); source2.method2 (); }}테스트 출력 :
Sourcable Interface의 첫 번째 Sub1!
Sourcable Interface의 두 번째 Sub2!
그것은 우리의 결과를 달성했습니다!
그것에 대해 이야기 한 후, 나는 세 가지 어댑터 모드의 응용 프로그램 시나리오를 요약 할 것입니다.
클래스 어댑터 모드 : 한 클래스를 다른 새 인터페이스를 만족시키는 클래스로 변환하려면 클래스 어댑터 모드를 사용하여 새 클래스를 만들고 원래 클래스를 상속 받고 새 인터페이스를 구현할 수 있습니다.
객체 어댑터 모드 : 객체를 다른 새 인터페이스를 만족시키는 객체로 변환하려면 원래 클래스의 인스턴스를 고정하고 래퍼 클래스 메소드에서 인스턴스 메소드를 호출 할 수 있습니다.
어댑터 인터페이스 모드 : 인터페이스에서 모든 메소드를 구현하지 않으려면 모든 방법을 구현하기 위해 초록 클래스 래퍼를 만들 수 있습니다. 우리가 다른 수업을 쓸 때, 추상 수업을 물려 받으십시오.
7. 데코레이터
이름에서 알 수 있듯이 장식 패턴은 객체에 새로운 기능을 추가하는 것이며, 동일한 인터페이스를 구현하기 위해 장식 객체와 장식 객체가 필요한 역동적입니다. 장식적인 물체는 장식적인 대상의 인스턴스를 가지고 있습니다. 관계 다이어그램은 다음과 같습니다.
소스 클래스는 장식 클래스이며, 데코레이터 클래스는 소스 클래스에 일부 함수를 동적으로 추가 할 수있는 장식 클래스입니다. 코드는 다음과 같습니다.
public interface sourcable {public void method (); } 공개 클래스 소스를 구현할 수있는 {@override public void method () {system.out.println ( "원래 메소드!"); }} 공개 클래스 데코레이터는 신맛이 나는 {private sourcable source를 구현합니다. 공개 데코레이터 (Sourcable Source) {super (); this.source = source; } @override public void method () {system.out.println ( "Decorator wefore!"); source.method (); System.out.println ( "데코레이터 이후!"); }}테스트 클래스 :
public class decoratortest {public static void main (String [] args) {sourcable source = new source (); Sourcable obj = 새로운 데코레이터 (소스); obj.method (); }}산출:
데코레이터 전에!
원래 방법!
데코레이터 후!
데코레이터 모드 응용 시나리오 :
1. 클래스의 함수를 확장해야합니다.
2. 객체에 함수를 동적으로 추가하고 동적으로 실행 취소 할 수도 있습니다. (상속은이를 수행 할 수 없습니다. 상속 함수는 정적이며 동적으로 추가 및 삭제할 수 없습니다.)
단점 : 너무 많은 비슷한 개체가 문제 해결하기 쉽지 않습니다!
8. 프록시 모드 (프록시)
실제로 각 모델 이름은 모델의 기능을 나타냅니다. 프록시 모델은 추가 에이전트 클래스를 추가하여 원래 객체에서 일부 작업을 수행하는 것입니다. 예를 들어, 우리가 집을 임대 할 때, 우리는 에이전트를 찾기 위해 돌아갑니다. 왜? 이 지역의 주택에 대한 정보에 대한 포괄적 인 이해가 없기 때문에, 나는 당신이 그것을 할 수 있도록 더 친숙한 사람을 찾기를 바랍니다. 이것이 여기에 대리인이 의미하는 바입니다. 예를 들어, 우리가 고소 할 때, 우리는 변호사가 법에 대한 전문 지식을 가지고 있고 우리를 대신하여 우리의 아이디어를 표현할 수 있기 때문에 변호사를 고용해야합니다. 먼저 관계 다이어그램을 살펴 보겠습니다.
위의 설명에 따르면 프록시 모드는 이해하기 쉽습니다. 코드를 살펴 보겠습니다.
public interface sourcable {public void method (); } 공개 클래스 소스를 구현할 수있는 {@override public void method () {system.out.println ( "원래 메소드!"); }} public class 프록시는 출현 가능한 {private source source; public proxy () {super (); this.source = 새로운 소스 (); } @override public void method () {prever (); source.method (); atfer (); } private void atfer() { System.out.println("after proxy!"); } private void before() { System.out.println("before proxy!"); }}测试类:
public class ProxyTest { public static void main(String[] args) { Sourceable source = new Proxy(); source.method(); }}산출:
before proxy!
the original method!
after proxy!
代理模式的应用场景:
如果已有的方法在使用的时候需要对原有的方法进行改进,此时有两种办法:
1、修改原有的方法来适应。这样违反了“对扩展开放,对修改关闭”的原则。
2、就是采用一个代理类调用原有的方法,且对产生的结果进行控制。这种方法就是代理模式。
使用代理模式,可以将功能划分的更加清晰,有助于后期维护!
9、外观模式(Facade)
外观模式是为了解决类与类之家的依赖关系的,像spring一样,可以将类和类之间的关系配置到配置文件中,而外观模式就是将他们的关系放在一个Facade类中,降低了类类之间的耦合度,该模式中没有涉及到接口,看下类图:(我们以一个计算机的启动过程为例)
我们先看下实现类:
public class CPU { public void startup(){ System.out.println("cpu startup!"); } public void shutdown(){ System.out.println("cpu shutdown!"); } } public class Memory { public void startup(){ System.out.println("memory startup!"); } public void shutdown(){ System.out.println("memory shutdown!"); } } public class Disk { public void startup(){ System.out.println("disk startup!"); } public void shutdown(){ System.out.println("disk shutdown!"); } } public class Computer { private CPU cpu; private Memory memory; private Disk disk; public Computer(){ cpu = new CPU(); memory = new Memory(); disk = new Disk(); } public void startup(){ System.out.println("start the computer!"); cpu.startup(); memory.startup(); disk.startup(); System.out.println("start computer finished!"); } public void shutdown(){ System.out.println("begin to close the computer!"); cpu.shutdown(); memory.shutdown(); disk.shutdown(); System.out.println("computer closed!"); }}User类如下:
public class User { public static void main(String[] args) { Computer computer = new Computer(); computer.startup(); computer.shutdown(); }}산출:
start the computer!
cpu startup!
memory startup!
disk startup!
start computer finished!
begin to close the computer!
cpu shutdown!
memory shutdown!
disk shutdown!
computer closed!
如果我们没有Computer类,那么,CPU、Memory、Disk他们之间将会相互持有实例,产生关系,这样会造成严重的依赖,修改一个类,可能会带来其他类的修改,这不是我们想要看到的,有了Computer类,他们之间的关系被放在了Computer类里,这样就起到了解耦的作用,这,就是外观模式!
10、桥接模式(Bridge)
桥接模式就是把事物和其具体实现分开,使他们可以各自独立的变化。桥接的用意是:将抽象化与实现化解耦,使得二者可以独立变化,像我们常用的JDBC桥DriverManager一样,JDBC进行连接数据库的时候,在各个数据库之间进行切换,基本不需要动太多的代码,甚至丝毫不用动,原因就是JDBC提供统一接口,每个数据库提供各自的实现,用一个叫做数据库驱动的程序来桥接就行了。我们来看看关系图:
实现代码:
先定义接口:
public interface Sourceable { public void method(); }分别定义两个实现类:
public class SourceSub1 implements Sourceable { @Override public void method() { System.out.println("this is the first sub!"); } } public class SourceSub2 implements Sourceable { @Override public void method() { System.out.println("this is the second sub!"); } }定义一个桥,持有Sourceable的一个实例:
public abstract class Bridge { private Sourceable source; public void method(){ source.method(); } public Sourceable getSource() { return source; } public void setSource(Sourceable source) { this.source = source; } } public class MyBridge extends Bridge { public void method(){ getSource().method(); } }测试类:
public class BridgeTest { public static void main(String[] args) { Bridge bridge = new MyBridge(); /*调用第一个对象*/ Sourceable source1 = new SourceSub1(); bridge.setSource(source1); bridge.method(); /*调用第二个对象*/ Sourceable source2 = new SourceSub2(); bridge.setSource(source2); bridge.method(); } }output:
this is the first sub!
this is the second sub!
这样,就通过对Bridge类的调用,实现了对接口Sourceable的实现类SourceSub1和SourceSub2的调用。接下来我再画个图,大家就应该明白了,因为这个图是我们JDBC连接的原理,有数据库学习基础的,一结合就都懂了。
11、组合模式(Composite)
组合模式有时又叫部分-整体模式在处理类似树形结构的问题时比较方便,看看关系图:
直接来看代码:
public class TreeNode { private String name; private TreeNode parent; private Vector<TreeNode> children = new Vector<TreeNode>(); public TreeNode(String name){ this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public TreeNode getParent() { return parent; } public void setParent(TreeNode parent) { this.parent = parent; } //添加孩子节点public void add(TreeNode node){ children.add(node); } //删除孩子节点public void remove(TreeNode node){ children.remove(node); } //取得孩子节点public Enumeration<TreeNode> getChildren(){ return children.elements(); } } public class Tree { TreeNode root = null; public Tree(String name) { root = new TreeNode(name); } public static void main(String[] args) { Tree tree = new Tree("A"); TreeNode nodeB = new TreeNode("B"); TreeNode nodeC = new TreeNode("C"); nodeB.add(nodeC); tree.root.add(nodeB); System.out.println("build the tree finished!"); } }使用场景:将多个对象组合在一起进行操作,常用于表示树形结构中,例如二叉树,数等。
12、享元模式(Flyweight)
享元模式的主要目的是实现对象的共享,即共享池,当系统中对象多的时候可以减少内存的开销,通常与工厂模式一起使用。
FlyWeightFactory负责创建和管理享元单元,当一个客户端请求时,工厂需要检查当前对象池中是否有符合条件的对象,如果有,就返回已经存在的对象,如果没有,则创建一个新对象,FlyWeight是超类。一提到共享池,我们很容易联想到Java里面的JDBC连接池,想想每个连接的特点,我们不难总结出:适用于作共享的一些个对象,他们有一些共有的属性,就拿数据库连接池来说,url、driverClassName、username、password及dbname,这些属性对于每个连接来说都是一样的,所以就适合用享元模式来处理,建一个工厂类,将上述类似属性作为内部数据,其它的作为外部数据,在方法调用时,当做参数传进来,这样就节省了空间,减少了实例的数量。
看个例子:
看下数据库连接池的代码:
public class ConnectionPool { private Vector<Connection> pool; /*公有属性*/ private String url = "jdbc:mysql://localhost:3306/test"; private String username = "root"; private String password = "root"; private String driverClassName = "com.mysql.jdbc.Driver"; private int poolSize = 100; private static ConnectionPool instance = null; Connection conn = null; /*构造方法,做一些初始化工作*/ private ConnectionPool() { pool = new Vector<Connection>(poolSize); for (int i = 0; i < poolSize; i++) { try { Class.forName(driverClassName); conn = DriverManager.getConnection(url, username, password); pool.add(conn); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } } } /* 返回连接到连接池*/ public synchronized void release() { pool.add(conn); } /* 返回连接池中的一个数据库连接*/ public synchronized Connection getConnection() { if (pool.size() > 0) { Connection conn = pool.get(0); pool.remove(conn); return conn; } else { return null; } } }通过连接池的管理,实现了数据库连接的共享,不需要每一次都重新创建连接,节省了数据库重新创建的开销,提升了系统的性能!本章讲解了7种结构型模式,因为篇幅的问题,剩下的11种行为型模式,
本章是关于设计模式的最后一讲,会讲到第三种设计模式――行为型模式,共11种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。这段时间一直在写关于设计模式的东西,终于写到一半了,写博文是个很费时间的东西,因为我得为读者负责,不论是图还是代码还是表述,都希望能尽量写清楚,以便读者理解,我想不论是我还是读者,都希望看到高质量的博文出来,从我本人出发,我会一直坚持下去,不断更新,源源动力来自于读者朋友们的不断支持,我会尽自己的努力,写好每一篇文章!希望大家能不断给出意见和建议,共同打造完美的博文!
先来张图,看看这11中模式的关系:
第一类:通过父类与子类的关系进行实现。第二类:两个类之间。第三类:类的状态。第四类:通过中间类
13、策略模式(strategy)
策略模式定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响到使用算法的客户。需要设计一个接口,为一系列实现类提供统一的方法,多个实现类实现该接口,设计一个抽象类(可有可无,属于辅助类),提供辅助函数,关系图如下:
图中ICalculator提供同意的方法,
AbstractCalculator是辅助类,提供辅助方法,接下来,依次实现下每个类:
首先统一接口:
public interface ICalculator { public int calculate(String exp); }辅助类:
public abstract class AbstractCalculator { public int[] split(String exp,String opt){ String array[] = exp.split(opt); int arrayInt[] = new int[2]; arrayInt[0] = Integer.parseInt(array[0]); arrayInt[1] = Integer.parseInt(array[1]); return arrayInt; } }三个实现类:
public class Plus extends AbstractCalculator implements ICalculator { @Override public int calculate(String exp) { int arrayInt[] = split(exp,"//+"); return arrayInt[0]+arrayInt[1]; } } public class Minus extends AbstractCalculator implements ICalculator { @Override public int calculate(String exp) { int arrayInt[] = split(exp,"-"); return arrayInt[0]-arrayInt[1]; } } public class Multiply extends AbstractCalculator implements ICalculator { @Override public int calculate(String exp) { int arrayInt[] = split(exp,"//*"); return arrayInt[0]*arrayInt[1]; } }简单的测试类:
public class StrategyTest { public static void main(String[] args) { String exp = "2+8"; ICalculator cal = new Plus(); int result = cal.calculate(exp); System.out.println(result); } }输出:10
策略模式的决定权在用户,系统本身提供不同算法的实现,新增或者删除算法,对各种算法做封装。因此,策略模式多用在算法决策系统中,外部用户只需要决定用哪个算法即可。
14、模板方法模式(Template Method)
解释一下模板方法模式,就是指:一个抽象类中,有一个主方法,再定义1...n个方法,可以是抽象的,也可以是实际的方法,定义一个类,继承该抽象类,重写抽象方法,通过调用抽象类,实现对子类的调用,先看个关系图:
就是在AbstractCalculator类中定义一个主方法calculate,calculate()调用spilt()等,Plus和Minus分别继承AbstractCalculator类,通过对AbstractCalculator的调用实现对子类的调用,看下面的例子:
public abstract class AbstractCalculator { /*主方法,实现对本类其它方法的调用*/ public final int calculate(String exp,String opt){ int array[] = split(exp,opt); return calculate(array[0],array[1]); } /*被子类重写的方法*/ abstract public int calculate(int num1,int num2); public int[] split(String exp,String opt){ String array[] = exp.split(opt); int arrayInt[] = new int[2]; arrayInt[0] = Integer.parseInt(array[0]); arrayInt[1] = Integer.parseInt(array[1]); return arrayInt; } } public class Plus extends AbstractCalculator { @Override public int calculate(int num1,int num2) { return num1 + num2; } }测试类:
public class StrategyTest { public static void main(String[] args) { String exp = "8+8"; AbstractCalculator cal = new Plus(); int result = cal.calculate(exp, "//+"); System.out.println(result); } }我跟踪下这个小程序的执行过程:首先将exp和"//+"做参数,调用AbstractCalculator类里的calculate(String,String)方法,在calculate(String,String)里调用同类的split(),之后再调用calculate(int ,int)方法,从这个方法进入到子类中,执行完return num1 + num2后,将值返回到AbstractCalculator类,赋给result,打印出来。正好验证了我们开头的思路。
15、观察者模式(Observer)
包括这个模式在内的接下来的四个模式,都是类和类之间的关系,不涉及到继承,学的时候应该记得归纳,记得本文最开始的那个图。观察者模式很好理解,类似于邮件订阅和RSS订阅,当我们浏览一些博客或wiki时,经常会看到RSS图标,就这的意思是,当你订阅了该文章,如果后续有更新,会及时通知你。其实,简单来讲就一句话:当一个对象变化时,其它依赖该对象的对象都会收到通知,并且随着变化!对象之间是一种一对多的关系。先来看看关系图:
我解释下这些类的作用:MySubject类就是我们的主对象,Observer1和Observer2是依赖于MySubject的对象,当MySubject变化时,Observer1和Observer2必然变化。AbstractSubject类中定义着需要监控的对象列表,可以对其进行修改:增加或删除被监控对象,且当MySubject变化时,负责通知在列表内存在的对象。我们看实现代码:
一个Observer接口:
public interface Observer { public void update(); }两个实现类:
public class Observer1 implements Observer { @Override public void update() { System.out.println("observer1 has received!"); } } public class Observer2 implements Observer { @Override public void update() { System.out.println("observer2 has received!"); } }Subject接口及实现类:
public interface Subject { /*增加观察者*/ public void add(Observer observer); /*删除观察者*/ public void del(Observer observer); /*通知所有的观察者*/ public void notifyObservers(); /*自身的操作*/ public void operation(); } public abstract class AbstractSubject implements Subject { private Vector<Observer> vector = new Vector<Observer>(); @Override public void add(Observer observer) { vector.add(observer); } @Override public void del(Observer observer) { vector.remove(observer); } @Override public void notifyObservers() { Enumeration<Observer> enumo = vector.elements(); while(enumo.hasMoreElements()){ enumo.nextElement().update(); } } } public class MySubject extends AbstractSubject { @Override public void operation() { System.out.println("update self!"); notifyObservers(); } }测试类:
public class ObserverTest { public static void main(String[] args) { Subject sub = new MySubject(); sub.add(new Observer1()); sub.add(new Observer2()); sub.operation(); } }산출:
update self!
observer1 has received!
observer2 has received!
这些东西,其实不难,只是有些抽象,不太容易整体理解,建议读者:根据关系图,新建项目,自己写代码(或者参考我的代码),按照总体思路走一遍,这样才能体会它的思想,理解起来容易!
16、迭代子模式(Iterator)
顾名思义,迭代器模式就是顺序访问聚集中的对象,一般来说,集合中非常常见,如果对集合类比较熟悉的话,理解本模式会十分轻松。这句话包含两层意思:一是需要遍历的对象,即聚集对象,二是迭代器对象,用于对聚集对象进行遍历访问。我们看下关系图:
这个思路和我们常用的一模一样,MyCollection中定义了集合的一些操作,MyIterator中定义了一系列迭代操作,且持有Collection实例,我们来看看实现代码:
两个接口:
public interface Collection { public Iterator iterator(); /*取得集合元素*/ public Object get(int i); /*取得集合大小*/ public int size(); } public interface Iterator { //前移public Object previous(); //后移public Object next(); public boolean hasNext(); //取得第一个元素public Object first(); }两个实现:
public class MyCollection implements Collection { public String string[] = {"A","B","C","D","E"}; @Override public Iterator iterator() { return new MyIterator(this); } @Override public Object get(int i) { return string[i]; } @Override public int size() { return string.length; } } public class MyIterator implements Iterator { private Collection collection; private int pos = -1; public MyIterator(Collection collection){ this.collection = collection; } @Override public Object previous() { if(pos > 0){ pos--; } return collection.get(pos); } @Override public Object next() { if(pos<collection.size()-1){ pos++; } return collection.get(pos); } @Override public boolean hasNext() { if(pos<collection.size()-1){ return true; }else{ return false; } } @Override public Object first() { pos = 0; return collection.get(pos); } }测试类:
public class Test { public static void main(String[] args) { Collection collection = new MyCollection(); Iterator it = collection.iterator(); while(it.hasNext()){ System.out.println(it.next()); } } }输出:ABCDE
此处我们貌似模拟了一个集合类的过程,感觉是不是很爽?其实JDK中各个类也都是这些基本的东西,加一些设计模式,再加一些优化放到一起的,只要我们把这些东西学会了,掌握好了,我们也可以写出自己的集合类,甚至框架!
17、责任链模式(Chain of Responsibility)
接下来我们将要谈谈责任链模式,有多个对象,每个对象持有对下一个对象的引用,这样就会形成一条链,请求在这条链上传递,直到某一对象决定处理该请求。但是发出者并不清楚到底最终那个对象会处理该请求,所以,责任链模式可以实现,在隐瞒客户端的情况下,对系统进行动态的调整。先看看关系图:
Abstracthandler类提供了get和set方法,方便MyHandle类设置和修改引用对象,MyHandle类是核心,实例化后生成一系列相互持有的对象,构成一条链。
public interface Handler { public void operator(); } public abstract class AbstractHandler { private Handler handler; public Handler getHandler() { return handler; } public void setHandler(Handler handler) { this.handler = handler; } } public class MyHandler extends AbstractHandler implements Handler { private String name; public MyHandler(String name) { this.name = name; } @Override public void operator() { System.out.println(name+"deal!"); if(getHandler()!=null){ getHandler().operator(); } } } public class Test { public static void main(String[] args) { MyHandler h1 = new MyHandler("h1"); MyHandler h2 = new MyHandler("h2"); MyHandler h3 = new MyHandler("h3"); h1.setHandler(h2); h2.setHandler(h3); h1.operator(); } }산출:
h1deal!
h2deal!
h3deal!
此处强调一点就是,链接上的请求可以是一条链,可以是一个树,还可以是一个环,模式本身不约束这个,需要我们自己去实现,同时,在一个时刻,命令只允许由一个对象传给另一个对象,而不允许传给多个对象。
18、命令模式(Command)
命令模式很好理解,举个例子,司令员下令让士兵去干件事情,从整个事情的角度来考虑,司令员的作用是,发出口令,口令经过传递,传到了士兵耳朵里,士兵去执行。这个过程好在,三者相互解耦,任何一方都不用去依赖其他人,只需要做好自己的事儿就行,司令员要的是结果,不会去关注到底士兵是怎么实现的。我们看看关系图:
Invoker是调用者(司令员),Receiver是被调用者(士兵),MyCommand是命令,实现了Command接口,持有接收对象,看实现代码:
public interface Command { public void exe(); } public class MyCommand implements Command { private Receiver receiver; public MyCommand(Receiver receiver) { this.receiver = receiver; } @Override public void exe() { receiver.action(); } } public class Receiver { public void action(){ System.out.println("command received!"); } } public class Invoker { private Command command; public Invoker(Command command) { this.command = command; } public void action(){ command.exe(); } } public class Test { public static void main(String[] args) { Receiver receiver = new Receiver(); Command cmd = new MyCommand(receiver); Invoker invoker = new Invoker(cmd); invoker.action(); } }输出:command received!
这个很哈理解,命令模式的目的就是达到命令的发出者和执行者之间解耦,实现请求和执行分开,熟悉Struts的同学应该知道,Struts其实就是一种将请求和呈现分离的技术,其中必然涉及命令模式的思想!
其实每个设计模式都是很重要的一种思想,看上去很熟,其实是因为我们在学到的东西中都有涉及,尽管有时我们并不知道,其实在Java本身的设计之中处处都有体现,像AWT、JDBC、集合类、IO管道或者是Web框架,里面设计模式无处不在。因为我们篇幅有限,很难讲每一个设计模式都讲的很详细,不过我会尽我所能,尽量在有限的空间和篇幅内,把意思写清楚了,更好让大家明白。本章不出意外的话,应该是设计模式最后一讲了,首先还是上一下上篇开头的那个图:
本章讲讲第三类和第四类。
19、备忘录模式(Memento)
主要目的是保存一个对象的某个状态,以便在适当的时候恢复对象,个人觉得叫备份模式更形象些,通俗的讲下:假设有原始类A,A中有各种属性,A可以决定需要备份的属性,备忘录类B是用来存储A的一些内部状态,类C呢,就是一个用来存储备忘录的,且只能存储,不能修改等操作。做个图来分析一下:
Original类是原始类,里面有需要保存的属性value及创建一个备忘录类,用来保存value值。Memento类是备忘录类,Storage类是存储备忘录的类,持有Memento类的实例,该模式很好理解。直接看源码:
public class Original { private String value; public String getValue() { return value; } public void setValue(String value) { this.value = value; } public Original(String value) { this.value = value; } public Memento createMemento(){ return new Memento(value); } public void restoreMemento(Memento memento){ this.value = memento.getValue(); } } public class Memento { private String value; public Memento(String value) { this.value = value; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } } public class Storage { private Memento memento; public Storage(Memento memento) { this.memento = memento; } public Memento getMemento() { return memento; } public void setMemento(Memento memento) { this.memento = memento; } }测试类:
public class Test { public static void main(String[] args) { // 创建原始类Original origi = new Original("egg"); // 创建备忘录Storage storage = new Storage(origi.createMemento()); // 修改原始类的状态System.out.println("初始化状态为:" + origi.getValue()); origi.setValue("niu"); System.out.println("修改后的状态为:" + origi.getValue()); // 回复原始类的状态origi.restoreMemento(storage.getMemento()); System.out.println("恢复后的状态为:" + origi.getValue()); } }산출:
初始化状态为:egg
修改后的状态为:niu
恢复后的状态为:egg
简单描述下:新建原始类时,value被初始化为egg,后经过修改,将value的值置为niu,最后倒数第二行进行恢复状态,结果成功恢复了。其实我觉得这个模式叫“备份-恢复”模式最形象。
20、状态模式(State)
核心思想就是:当对象的状态改变时,同时改变其行为,很好理解!就拿QQ来说,有几种状态,在线、隐身、忙碌等,每个状态对应不同的操作,而且你的好友也能看到你的状态,所以,状态模式就两点:1、可以通过改变状态来获得不同的行为。2、你的好友能同时看到你的变化。看图:
State类是个状态类,Context类可以实现切换,我们来看看代码:
package com.xtfggef.dp.state; /** * 状态类的核心类* 2012-12-1 * @author erqing * */ public class State { private String value; public String getValue() { return value; } public void setValue(String value) { this.value = value; } public void method1(){ System.out.println("execute the first opt!"); } public void method2(){ System.out.println("execute the second opt!"); } } package com.xtfggef.dp.state; /** * 状态模式的切换类2012-12-1 * @author erqing * */ public class Context { private State state; public Context(State state) { this.state = state; } public State getState() { return state; } public void setState(State state) { this.state = state; } public void method() { if (state.getValue().equals("state1")) { state.method1(); } else if (state.getValue().equals("state2")) { state.method2(); } } }测试类:
public class Test { public static void main(String[] args) { State state = new State(); Context context = new Context(state); //设置第一种状态state.setValue("state1"); context.method(); //设置第二种状态state.setValue("state2"); context.method(); } } 산출:
execute the first opt!
execute the second opt!
根据这个特性,状态模式在日常开发中用的挺多的,尤其是做网站的时候,我们有时希望根据对象的某一属性,区别开他们的一些功能,比如说简单的权限控制等。
21、访问者模式(Visitor)
访问者模式把数据结构和作用于结构上的操作解耦合,使得操作集合可相对自由地演化。访问者模式适用于数据结构相对稳定算法又易变化的系统。因为访问者模式使得算法操作增加变得容易。若系统数据结构对象易于变化,经常有新的数据对象增加进来,则不适合使用访问者模式。访问者模式的优点是增加操作很容易,因为增加操作意味着增加新的访问者。访问者模式将有关行为集中到一个访问者对象中,其改变不影响系统数据结构。其缺点就是增加新的数据结构很困难。―― From 百科
简单来说,访问者模式就是一种分离对象数据结构与行为的方法,通过这种分离,可达到为一个被访问者动态添加新的操作而无需做其它的修改的效果。简单关系图:
来看看原码:一个Visitor类,存放要访问的对象,
public interface Visitor { public void visit(Subject sub); } public class MyVisitor implements Visitor { @Override public void visit(Subject sub) { System.out.println("visit the subject:"+sub.getSubject()); } }Subject类,accept方法,接受将要访问它的对象,getSubject()获取将要被访问的属性,
public interface Subject { public void accept(Visitor visitor); public String getSubject(); } public class MySubject implements Subject { @Override public void accept(Visitor visitor) { visitor.visit(this); } @Override public String getSubject() { return "love"; } } 시험:
public class Test { public static void main(String[] args) { Visitor visitor = new MyVisitor(); Subject sub = new MySubject(); sub.accept(visitor); } }输出:visit the subject:love
该模式适用场景:如果我们想为一个现有的类增加新功能,不得不考虑几个事情:1、新功能会不会与现有功能出现兼容性问题?2、以后会不会再需要添加?3、如果类不允许修改代码怎么办?面对这些问题,最好的解决方法就是使用访问者模式,访问者模式适用于数据结构相对稳定的系统,把数据结构和算法解耦,
22、中介者模式(Mediator)
中介者模式也是用来降低类类之间的耦合的,因为如果类类之间有依赖关系的话,不利于功能的拓展和维护,因为只要修改一个对象,其它关联的对象都得进行修改。如果使用中介者模式,只需关心和Mediator类的关系,具体类类之间的关系及调度交给Mediator就行,这有点像spring容器的作用。先看看图:
User类统一接口,User1和User2分别是不同的对象,二者之间有关联,如果不采用中介者模式,则需要二者相互持有引用,这样二者的耦合度很高,为了解耦,引入了Mediator类,提供统一接口,MyMediator为其实现类,里面持有User1和User2的实例,用来实现对User1和User2的控制。这样User1和User2两个对象相互独立,他们只需要保持好和Mediator之间的关系就行,剩下的全由MyMediator类来维护!基本实现:
public interface Mediator { public void createMediator(); public void workAll(); } public class MyMediator implements Mediator { private User user1; private User user2; public User getUser1() { return user1; } public User getUser2() { return user2; } @Override public void createMediator() { user1 = new User1(this); user2 = new User2(this); } @Override public void workAll() { user1.work(); user2.work(); } } public abstract class User { private Mediator mediator; public Mediator getMediator(){ return mediator; } public User(Mediator mediator) { this.mediator = mediator; } public abstract void work(); } public class User1 extends User { public User1(Mediator mediator){ super(mediator); } @Override public void work() { System.out.println("user1 exe!"); } } public class User2 extends User { public User2(Mediator mediator){ super(mediator); } @Override public void work() { System.out.println("user2 exe!"); } }测试类:
public class Test { public static void main(String[] args) { Mediator mediator = new MyMediator(); mediator.createMediator(); mediator.workAll(); } } 산출:
user1 exe!
user2 exe!
23、解释器模式(Interpreter)
解释器模式是我们暂时的最后一讲,一般主要应用在OOP开发中的编译器的开发中,所以适用面比较窄。
Context类是一个上下文环境类,Plus和Minus分别是用来计算的实现,代码如下:
public interface Expression { public int interpret(Context context); } public class Plus implements Expression { @Override public int interpret(Context context) { return context.getNum1()+context.getNum2(); } } public class Minus implements Expression { @Override public int interpret(Context context) { return context.getNum1()-context.getNum2(); } } public class Context { private int num1; private int num2; public Context(int num1, int num2) { this.num1 = num1; this.num2 = num2; } public int getNum1() { return num1; } public void setNum1(int num1) { this.num1 = num1; } public int getNum2() { return num2; } public void setNum2(int num2) { this.num2 = num2; } } public class Test { public static void main(String[] args) { // 计算9+2-8的值int result = new Minus().interpret((new Context(new Plus() .interpret(new Context(9, 2)), 8))); System.out.println(result); } }最后输出正确的结果:3。
基本就这样,解释器模式用来做各种各样的解释器,如正则表达式等的解释器等等!
原文链接:http://www.cnblogs.com/maowang1991/archive/2013/04/15/3023236.html
위는이 기사의 모든 내용입니다. 모든 사람의 학습에 도움이되기를 바랍니다. 모든 사람이 wulin.com을 더 지원하기를 바랍니다.